This is an archive of the discontinued LLVM Phabricator instance.

[Clang][AArch64] Add/implement ACLE keywords for SME.
ClosedPublic

Authored by sdesmalen on Jun 14 2022, 9:32 AM.

Details

Summary

This patch adds all the language-level function keywords defined in:

https://github.com/ARM-software/acle/pull/188 (merged)
https://github.com/ARM-software/acle/pull/261 (update after D148700 landed)

The keywords are used to control PSTATE.ZA and PSTATE.SM, which are
respectively used for enabling the use of the ZA matrix array and Streaming
mode. This information needs to be available on call sites, since the use
of ZA or streaming mode may have to be enabled or disabled around the
call-site (depending on the IR attributes set on the caller and the
callee). For calls to functions from a function pointer, there is no IR
declaration available, so the IR attributes must be added explicitly to the
call-site.

With the exception of 'arm_locally_streaming' and 'arm_new_za' the
information is part of the function's interface, not just the function
definition, and thus needs to be propagated through the
FunctionProtoType::ExtProtoInfo.

This patch adds the defintions of these keywords, as well as codegen and
semantic analysis to ensure conversions between function pointers are valid
and that no conflicting keywords are set. For example, 'arm_streaming'
and '
arm_streaming_compatible' are mutually exclusive.

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
  • Removed attribute handling from SemaDeclAttr.cpp, so that the type attributes are only handled in SemaType.cpp
  • Made ArmLocallyStreaming a DeclOrTypeAttribute again.
  • Added new tests.

Thanks for your patience reviewing this patch @aaron.ballman!

clang/include/clang/AST/Type.h
4064

This field is added to ExtProtoInfo struct, which is used to populate the ExtraBitfields in the FunctionPrototype's AST node.

Sorry if this wasn't clear because the diff had no context. arc diff wasn't working for me, so manually updated the patch, but then forgot to add full context.

clang/include/clang/Basic/Attr.td
2322

If it's an attribute that applies to the type of the function, you should not be allowed to specify it on the declaration

Could you maybe give me an example here of what it looks like to apply the attribute to the function *type* when it's not allowed on the *declaration*? Does this all have to do with the position of the attribute in the declaration statement? When writing a function declaration it defines both the name and the type of that function, so there's no way to write a declaration without also defining its type.

Maybe it helps if you have any pointers that explain the difference in syntax between function declaration/function type attributes (https://clang.llvm.org/docs/AttributeReference.html doesn't really address this)

2333

Okay, I've removed the handling in SemaDeclAttr, so that it is only handled in SemaType.cpp.

But it still allows the attribute to be applied to any position in the function declaration, i.e.

__attribute__((arm_streaming)) float foo1(int);
float __attribute__((arm_streaming)) foo2(int);
float foo3(int) __attribute__((arm_streaming));

Is that expected?

2345

You're right that it shouldn't be. I want it to just be a decl attribute and don't want it to be either a Type or a Stmt attribute, but that doesn't seem to exist.
I find their meaning quite confusing, because both allow me to limit their applicability to Functions. What is the right class to derive from here?

clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

This error and the one below make me wonder whether an attribute spelling is the appropriate way to surface this functionality as opposed to a keyword.

I'm not sure I understand what you mean, do you have an example?

clang/test/Sema/aarch64-sme-func-attrs.c
213

Great suggestions, thanks! They both seem to be handled correctly.

Thanks for your patience reviewing this patch @aaron.ballman!

Happy to help, thank you for your patience with my constant stream of questions about the design. :-)

clang/include/clang/AST/Type.h
4064

Ah, I see what's going on there, thank you (the extra context helped as well).

clang/include/clang/Basic/Attr.td
2333

https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax

"For compatibility with existing code written for compiler versions that did not implement attributes on nested declarators, some laxity is allowed in the placing of attributes. If an attribute that only applies to types is applied to a declaration, it is treated as applying to the type of that declaration. If an attribute that only applies to declarations is applied to the type of a declaration, it is treated as applying to that declaration; and, for compatibility with code placing the attributes immediately before the identifier declared, such an attribute applied to a function return type is treated as applying to the function type, and such an attribute applied to an array element type is treated as applying to the array type. If an attribute that only applies to function types is applied to a pointer-to-function type, it is treated as applying to the pointer target type; if such an attribute is applied to a function return type that is not a pointer-to-function type, it is treated as applying to the function type."

So yes, that's expected because the attribute only applies to types, so it magically "slides" onto the type regardless of where it was written on the declaration. By making it a type only attribute, it's now clear what the GNU attribute applies to regardless of where it's written.

2345

Hmm, we have some documentation at https://clang.llvm.org/docs/InternalsManual.html#how-to-add-an-attribute but it's not very clear on all the choices and when to pick which one.

For declaration attributes on a function, the question really boils down to whether you want redeclarations to automatically inherit the attribute or not. e.g.,

__attribute__((arm_locally_streaming)) void func(void);
void func(void); // Is this declaration also marked locally streaming?

void use(void) {
  func(); // Does this use see the attribute or not?
}

If you want the call in use() to see the attribute, then you want the attribute to be inherited on the redeclaration, and so you'd use InheritableAttr. If you don't want the call in use() to see the attribute, then you'd use Attr.

clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

override and final could have been surfaced via attributes, and in Clang we semantically express them as such (see Final and Override in Attr.td), but instead they are surfaced to the user as keywords in the language standard. You're not under the same constraints as the standard (you're making a vendor-specific attribute). We're not super consistent with whether we use the same approach as the standard (we have some type attributes that are spelled as attributes like vector_size and others that are spelled via keywords like _Nullable.

So you could spell your type attributes the way you have been with __attribute__, or you could come up with keywords for them (so instead of using GNU<"whatever"> for the attribute, you could use Keyword<_Whatever> or Keyword<__whatever> (you'd also need to add them as recognized keyword tokens, parse them as appropriate, etc).

Note: I don't insist on you using a keyword for these, but I am wondering if that approach was considered or not given how non-portable the attributes are (if an implementation ignores this attribute, it sounds like the program semantics are unlikely to be correct).

clang/lib/Sema/SemaType.cpp
7669–7672

I'm sorry, I see now that I forgot to say the important bit that was in my head: we don't check those subjects automatically yet, but we do want to add more automation to type attribute processing as we've already done for declarations and statements, so we want the content in Attr.td to be as correct as possible even though it still needs to be manually checked. That makes it easier on us when we go to add the automation; there's less "which is correct, the SemaType.cpp code or the Attr.td declaration?" involved).

Matt added a subscriber: Matt.Oct 19 2022, 5:20 AM
sdesmalen updated this revision to Diff 471515.Oct 28 2022, 6:08 AM
sdesmalen marked 9 inline comments as done.
sdesmalen edited the summary of this revision. (Show Details)
  • Rebased patch
  • Use InheritableAttr for the ArmLocallyStreaming attribute instead of DeclOrTypeAttr
  • Added test-case to test inheritable property of ArmLocallyStreaming
  • Refactored enum as suggested

The past few months we've worked to get the attributes at the LLVM IR side implemented. Since that work has now landed, this patch should no longer be held up by the LLVM side of things.

@aaron.ballman I've updated and rebased the patch. I went through all the comments again and believe that all of them have been addressed. Would you be happy to give this patch another look?

clang/include/clang/Basic/Attr.td
2345

Thanks for clarifying. I've changed the code to use InheritableAttr, since future redeclarations/redefinitions should inherit the arm_locally_streaming attribute (and also added an extra test-case for this)

clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

@rsandifo-arm can you shed some light on whether using a keyword instead of an attribute was considered?

rsandifo-arm added inline comments.Oct 28 2022, 11:35 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

Thanks @aaron.ballman for the reviews.

Yeah, we did consider using keywords instead. The main reason for sticking with GNU attributes is that they were specifically designed as an extensible way of attaching information or requirements to the source code, and they provide well-settled semantics. It seemed better to reuse an existing mechanism rather than invent something new.

Also, as C++ evolves, the semantics of GNU attributes evolve to match. If we surface the SME features as GNU attributes, we inherit this development in the underlying semantics, without having to maintain our own evolving specification for where the keywords can be placed. For example, when lambdas were introduced, GNU attributes were extended to support lambdas. Something similar could happen in future.

We could have defined the semantics of the keywords to be that they behave exactly like GNU attributes. However:

  1. If we do that, the advantage of using a keyword is less obvious. (I can see the argument that the syntax looks nicer though.)
  2. Like you say in the review, the semantics of GNU attributes carry some historical baggage. If would be difficult to justify keeping that historical baggage for something that is ostensibly new and not a GNU attribute as such.

A minor, but still nontrivial, reason is that there is less appetite in the GCC community for supporting target-specific extensions to the core language. Adding a target-specific keyword is likely to be rejected. It would be acceptable to make the “keyword” be a predefined macro that expands to a GNU attribute under the hood, but I don't think that would address the core issue.

I can definitely understand the unease about using attributes for things that affect semantics. Like you say, that's going against a core principle of the standard-defined attributes. But I think in a way, it's unfortunate that GNU-style attributes and standard-defined attributes are both called “attributes”, because I don't think there's a prohibition (even in theory) against GNU attributes affecting semantics. I think GNU attributes are designed to be more general than that, probably through historical accretion rather than an up-front choice.

Like you say, vector_size is one example of something that significantly affect semantics. But there are quite a few others too. For example:

  • GNU targets traditionally provide access to naked functions and interrupt handlers through attributes.
  • Different calling conventions also use attributes.
  • The target attribute switches between ISAs in ways that do affect semantics.
  • always_inline functions must be inlined for correctness.
  • weak and visibility in effect alter the ODR.
  • In GCC, functions that return twice (like setjmp) must be declared returns_twice.

It's unfortunate that using SME attributes will only trigger a warning in older compilers. The warning is enabled by default though. And in practice, I think most SME code would rely on other constructs that older compilers wouldn't provide, such as intrinsics.

Gentle ping :)

Adding Erich as the new attributes code owner.

clang/include/clang/Basic/Attr.td
2322

Could you maybe give me an example here of what it looks like to apply the attribute to the function *type* when it's not allowed on the *declaration*? Does this all have to do with the position of the attribute in the declaration statement? When writing a function declaration it defines both the name and the type of that function, so there's no way to write a declaration without also defining its type.

Sure, but I'm going to use [[]] syntax to demonstrate the difference because it's very hard to reason about *what* a GNU-style attribute applies to given it's "sliding" behavior. C++ attributes have rules about what they appertain to based on syntactic location of the attribute so it's an easier way to demonstrate.

https://godbolt.org/z/Yz37fTbWY demonstrates some of the differences in terms of positioning of the attribute.
https://godbolt.org/z/18Y1Pn4se demonstrates the behavior when the type attribute matters for the type identify of the function.
https://godbolt.org/z/1jTqPx4f4 demonstrates another way in which a type attribute can matter.

For __attribute__ it's harder to reason about because of https://gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/attribute-syntax.html, especially because GCC and Clang don't always agree in terms of whether something can be used as a type attribute or not. But in terms of the mental model of attributes, the question really boils down to how much the attribute matters to the identity of the type -- if you don't want users to be able to assign an attributed function to a nonattributed function pointer (or vice versa), it most likely is a type attribute. If you think presence or absence of the attribute should impact things like overload resolution or template specializations, it's almost certainly a type attribute.

clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

Yeah, we did consider using keywords instead. The main reason for sticking with GNU attributes is that they were specifically designed as an extensible way of attaching information or requirements to the source code, and they provide well-settled semantics. It seemed better to reuse an existing mechanism rather than invent something new.

If this is extra information the compiler is free to ignore without impacting program correctness, then I think an attribute is fine. But if the user's code will silently break if the attribute is ignored (e.g., an attribute ignored warning happens but the program successfully translates and does bad things at runtime), in some ways use of an attribute is far less ideal than use of a keyword-based attribute specifically because of the syntax error the user would get. Some users really like the "unknown attribute ignored" warning (like me!) and ensure it stays enabled and others users really don't like that warning and disable it. So it's dangerous to assume there's anything there worth relying on if the attributes have security implications at runtime.

Also, as C++ evolves, the semantics of GNU attributes evolve to match.

Errr does it? https://gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/attribute-syntax.html#attribute-syntax "There are some problems with the semantics of attributes in C++. For example, there are no manglings for attributes, although they may affect code generation, so problems may arise when attributed types are used in conjunction with templates or overloading. Similarly, typeid does not distinguish between types with different attributes. Support for attributes in C++ may be restricted in future to attributes on declarations only, but not on nested declarators." doesn't really seem to support that position.

A minor, but still nontrivial, reason is that there is less appetite in the GCC community for supporting target-specific extensions to the core language.

That's unfortunate given that _Keywords are reserved to the implementation specifically for this sort of thing. _Nullable is a good example of a successful attribute keyword. Attributes are a target-specific extension to the core language regardless of what syntax they use, so to me the driving question is more "what user experience do you want?" and less "is this an extension to the core language?"

erichkeane added inline comments.Nov 15 2022, 6:29 AM
clang/include/clang/AST/Type.h
3926
3940

We seem to be missing all of the modules-storage code for these. Since this is modifying the AST, we need to increment the 'breaking change' AST code, plus add this to the ASTWriter/ASTReader interface.

4008

Are they aware that includes; void Baz(); ?

sdesmalen added inline comments.Nov 15 2022, 9:26 AM
clang/include/clang/AST/Type.h
3940

Since this is modifying the AST, we need to increment the 'breaking change' AST code

Could you give me some pointers on what you expect to see changed here? I understand your point about adding this to the ASTWriter/Reader interfaces for module-storage, but it's not entirely clear what you mean by "increment the breaking change AST code".

I see there is also an ASTImporter, I guess this different from the ASTReader?

Please apologise my ignorance here, I'm not as familiar with the Clang codebase.

I also don't see anything in this with how it works with function pointer conversions, how it affects template instantiation, overload resolution/etc. If this is a type attribute, we need to specify all of that, probably in a separate document, since this is such a change to all those things.

clang/include/clang/AST/Type.h
3940

See VersionMajor here: https://clang.llvm.org/doxygen/ASTBitCodes_8h_source.html

Yes, ASTReader/ASTWriter and ASTImporter are different unfortunately. I'm not completely sure of the difference, but doing this patch changing the type here without doing those will break modules.

At some point, you should also add a release note to tell users about the new functionality.

clang/include/clang/AST/Type.h
4008

FWIW, the linked docs say:

> Functions like `some_func` and `another_func` are referred to as
> (K&R-style) “unprototyped” functions. The first C standard categorized
> them as an obsolescent feature and C18 removed all remaining support
> for them.

That's not quite accurate. They've always been obsolete in standard C (both ANSI and ISO C), so that's correct. But it's C2x that removes all remaining support for them. (Also, there's no such release as C18, there's C11, C17, and expected to be C23).

Are they aware that includes; void Baz(); ?

Just to expound on this question: this means void baz(); in C17 mode cannot have the attribute but void baz(); in C2x mode can. Folks coming from C++ tend to expect the C2x behavior to be the default and so it's plausible to see such signatures in older language modes. Will that cause any pain or confusion for you?

clang/include/clang/Basic/AttrDocs.td
6318

There are enough of these attributes that I think we should consider adding an ARM C Language Extensions for SME documentation category:

def DocCatACLE_SME : DocumentationCategory<"ARM C Language Extensions for SME"> {
  let Content = [{
Put general information about what ACLE SME is here to give an overview of the functionality and a place to link to the official ACLE SME documentation for more in-depth details.
}];
}

and then use this for the Category for each of the attributes so they group together.

6334–6336

I'm not certain I understand what this is telling me. The function is expected to be called with PSTATE.SM == 0 and return with it unchanged, so what does it mean for it to also require PSTATE.SM to be 1? I *think* it might be saying that the function is called with SM = 0 and returns with SM = 0, but within the function body itself SM will be 1?

6366–6367

Is acculator a typo or is that a term of art I am unfamiliar with?

6389–6396

If this is a hint, why is the attribute part of the type system? It seems like it'd be fine for the hint to be lost when forming a function pointer, as in:

__attribute__((arm_preserves_za)) void func(void);

void foo(void) {
  void (*fp)(void) = func; // Doesn't need to be an error, right?
}
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

Re-pinging this part of the thread -- the more I review this, the more it sounds like ignoring these attributes will cause a significant likelihood of the user's program silently misbehaving. Is that accurate? If so, I would reconsider whether using a keyword gives a more appropriate user experience for portability. With an attribute, the user has a very real chance of their code continuing to compile but not work. With a keyword, the user will get errors when porting to a compiler that doesn't support the keyword and are more protected from miscompiles. We can still reuse the vast majority of the implementation work already done here, so it should not be a major change to the implementation (a bit of extra parsing work for the keywords is really the only thing missing).

clang/lib/Basic/Targets/AArch64.cpp
589–590

I don't see any test coverage for these changes, and it seems orthogonal -- should this be split into its own review?

clang/lib/CodeGen/CGCall.cpp
2231–2247

The declaration won't have most of these attributes because they're type attributes, not declaration attributes. This looks like mostly dead code.

clang/lib/Sema/SemaDecl.cpp
3746–3753

How should these work with template (partial) specializations? Should it be invalid to write this on the primary template? Should it be invalid if the specialization doesn't match the primary template?

clang/lib/Sema/SemaDeclAttr.cpp
278

I think this is likely a good change but it seems orthogonal to the rest of the patch (it'd be helpful to more clearly see what test behavior this changes).

clang/lib/Sema/SemaDeclCXX.cpp
17665–17666
clang/lib/Sema/SemaType.cpp
7659

Pretty sure this should still work, assuming OtherAttr is an Attr * after stripping off the iterator.

7816–7840

Not your problem, but someday we should tablegen this kind of mutual exclusion the same as we've done for declaration and statement attributes.

paulwalker-arm added a comment.EditedNov 16 2022, 8:09 AM

Hi @rsandifo-arm , what are your thoughts on Arron's observations? My interpretation is that Arm originally figured the distinction between keywords and gnu attributes was minimal and thus using our previous norms made most sense. This is not my world so my understanding is somewhat naive but it does sound like Arron has raised some good reasons why keywords are a better design decision. Do you agree? I wonder if it's worth having this conversation on https://github.com/ARM-software/abi-aa/pull/123 (unless Richard has a better link) so we can capture the rational for future design changes if any. @aaron.ballman, are you ok with this or do you prefer to keep the design conversation linked to this clang implementation?

Hi @rsandifo-arm , what are your thoughts on Arron's observations? My interpretation is that Arm originally figured the distinction between keywords and gnu attributes was minimal and thus using our previous norms made most sense. This is not my world so my understanding is somewhat naive but it does sound like Arron has raised some good reasons why keywords are a better design decision. Do you agree? I wonder if it's worth having this conversation on https://github.com/ARM-software/abi-aa/pull/123 (unless Richard has a better link) so we can capture the rational for future design changes if any. @aaron.ballman, are you ok with this or do you prefer to keep the design conversation linked to this clang implementation?

I'm fine having that conversation on the design document rather than here on the review. We can use links to connect the dots between the design pull request and the phab review.

rsandifo-arm added inline comments.Nov 16 2022, 10:54 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

I accept your points about the dangers of ignoring unsupported attributes. But this isn't an SME-specific issue. Ignoring the other GNU attributes I mentioned would also silently generate wrong code. I think this is a very strong argument against suppressing warnings about unrecognised GNU attributes by default, even if warnings about unrecognised standard attributes are eventually suppressed by default. (Perhaps they should even be controllable separately?) Like I say, there doesn't seem to have been a principle that GNU attributes provide information that the compiler is free to ignore.

If we did add keywords, would you be OK with giving them exactly the same semantics as GNU attributes? In other words, would it be acceptable to treat the keywords as essentially a one-for-one parsing replacement for GNU attributes? Or would you want the keyword to follow the same distinction between type properties and object properties as the standard attributes do?

rsandifo-arm added inline comments.Nov 16 2022, 11:08 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

Or would you want the keyword to follow the same distinction between type properties and object properties as the standard attributes do?

That was a vague question, sorry. What I meant is whether the correct position of the keyword should vary based on whether it describes a property of the function type vs. a property of the function definition, or whether we can maintain the laxity in the GNU attribute positioning. I think the laxity in the GNU attribute positioning is helpful to users, and in this case wouldn't introduce any ambiguity.

From discussions I've had with people who will write/are writing SME code, the distinction between arm_locally_streamng being a property of the definition and arm_streaming being a property of the type seems contrived to them, especially when the usage of SME is private to a single translation unit.

Treating the keyword as an error-generating GNU attribute stand-in would also allow implementations to make the keyword as a preprocessor macro, if they need to.

aaron.ballman added inline comments.Nov 16 2022, 11:28 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

If we did add keywords, would you be OK with giving them exactly the same semantics as GNU attributes?

The way we do keyword attributes is that the parser is responsible for parsing the keyword and kicking off the creation of the attribute in the proper place. For example: https://github.com/llvm/llvm-project/blob/main/clang/lib/Parse/ParseDecl.cpp#L928. This approach means there's plenty of flexibility available to parse the keyword where you think it makes sense.

That said, function types don't always allow for qualifiers (for example, free functions in C++ and all functions in C), so I would expect something more like _Keyword int func(); for functions and probably like int (* _Keyword)(); for function pointers. However, I agree that it's a bit more awkward for SME needs than the nullability attributes because the nullability attributes map directly onto qualifiers and so the question of 'where do we parse this' was very easy to answer.

All this said, if there's opposition to using keywords and a preference for attributes, that is okay by me (we have other type attributes). I was bringing it up mostly to make sure the design was considered explicitly rather than thinking an attribute was the only viable approach.

rsandifo-arm added inline comments.Nov 22 2022, 12:52 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

The way we do keyword attributes is that the parser is responsible for parsing the keyword and kicking off the creation of the attribute in the proper place. For example: https://github.com/llvm/llvm-project/blob/main/clang/lib/Parse/ParseDecl.cpp#L928. This approach means there's plenty of flexibility available to parse the keyword where you think it makes sense.

That said, function types don't always allow for qualifiers (for example, free functions in C++ and all functions in C), so I would expect something more like _Keyword int func(); for functions and probably like int (* _Keyword)(); for function pointers.

Thanks. Having a consistent position like _Keyword int func(); for both kinds of attribute (decl and type) sounds good.

If we did add keywords, what kinds of spelling would you suggest? In the GNU attribute names we used the arm_ prefix as a kind of old-school namespace emulation. If the information had been suitable for standard attributes, we would obviously have used arm:: instead. However, using namespace-like prefixes might not be as natural for keywords, and I couldn't see any existing examples in TokenKinds.def. Would names like _NewZA/__new_za be OK? Would you recommend the CamelCase or lower_case style?

If we did use unprefixed keywords, would _Streaming/__streaming be acceptable for streaming functions? Or, since the term “streaming” is pretty generic, should we try to make the spelling more specific to AArch64 or SME?

Also, in terms of Clang direction, would it be acceptable to continue to add target-specific keywords for each new feature that compilers must interpret for correctness, and handle them directly in things like the parser case statements? Or should there be a more generic way of handling this, similar to Attr.td? The reason for asking is that it seems like the list of keywords could become quite long, just like Attr.td has. I don't think the SME attributes/information are particularly unusual, and it seems a lot of existing attributes would have been handled this way if we were starting from scratch, including all those that affect calling conventions. (There are some calling conventions that already have keywords, but there are some like preserve_most and preserve_all that seem to be handled by attributes only. There's also the Arm-specific aarch64_vector_pcs.)

sdesmalen updated this revision to Diff 477116.Nov 22 2022, 1:58 AM
sdesmalen marked 10 inline comments as done.

Addressed review comments. These include:

  • Allow dropping the arm_preserves_za attribute.
  • Added tests for serializing/deserializing the AST.
  • Changed descriptions in documentation.

Also fixed an issue where the attributes would previously be silently be dropped when the function being converted to did not have a function prototype.

clang/include/clang/AST/Type.h
3940

So I tried to create some tests for this (see clang/test/AST/ast-dump-sme-attributes.cpp) and realised that the serialization/deserialization works out-of-the-box.

Does that mean all is needed is an increment of the VERSION_MAJOR or VERSION_MINOR number?

4008

@rsandifo-arm explained to me that it is indeed a deliberate choice not to support the attribute on unprototyped functions, as these are an obsolescent feature that will be removed in C2x. The mitigation should be trivial for new code that is written and it makes new code compliant with the next version of the standard.

clang/include/clang/Basic/Attr.td
2322

Thank you, those examples are very helpful!

clang/include/clang/Basic/AttrDocs.td
6334–6336

Yes, that's what I was trying to describe, albeit in a slightly confusing way :) I've rephrased it!

6366–6367

That was a typo, well spotted.

6389–6396

You're right, this should not lead to an error, the ACLE specification makes an exception for arm_preserves_za:

Function types with this attribute implicitly convert to function types that do not have the attribute. However, the reverse is not true.

I've fixed that and adjusted the test accordingly (both conversions are tested)

clang/lib/Basic/Targets/AArch64.cpp
589–590

This is actually tested in clang/test/Sema/aarch64-sme-attrs-no-sme.c, where it gives a warning diagnostic that the attribute is ignored when +sme is not specified.

clang/lib/CodeGen/CGCall.cpp
2231–2247

Good spot, I only need to handle aarch64_pstate_sm_body because that is a declaration attribute, not a type attribute.

clang/lib/Sema/SemaDecl.cpp
3746–3753

Can you share an example of what you're thinking of?

Do you mean something like:

template<typename T> void (*a)(T, T);
template<> __attribute__((arm_streaming)) void (*a<int>)(int, int);

where calling a<short>(0, 0) and a<int>(0, 0) have different streaming-mode behaviour? If so, then that is the intent. The SME attributes behave similar to a calling convention attributes, with the difference that more than one attribute can be applied.

clang/lib/Sema/SemaDeclAttr.cpp
278

It turns out this change is no longer needed, I'll remove it.

aaron.ballman added inline comments.Nov 22 2022, 8:15 AM
clang/include/clang/AST/Type.h
4008

Perfect, thank you for double-checking!

clang/include/clang/Basic/AttrDocs.td
6322

Double-checking: that macro is missing the trailing double underscore, is that expected?

clang/include/clang/Basic/DiagnosticSemaKinds.td
2000

If we did add keywords, what kinds of spelling would you suggest?

Ooof, yeah, you're asking some good questions about names! I'm hesitant to take names that are generic because of the potential for conflicts with other targets. However, for concepts like streaming, using _Streaming as the keyword is rather appealing too. The one scenario that really makes me think we should *not* use generic keywords is if the user tries to combine extensions. e.g., if ARM SVE uses _Streaming for one purpose and SYCL uses _Streaming for a slightly different purpose, it becomes impossible to use SYCL and ARM SVE together in some circumstances because we might not know which semantics to pick for any given use of the keyword. That makes me think _Arm_Streaming or __arm_streaming or whatever is a better approach. But as you point out, our existing keyword attributes don't encode a prefix in their names so this is novel. In terms of camel case vs lower case, I don't have a strong opinion on which to go with; we've used both in the past (__global and __constant vs _Nullable and, but __lower_case is by far the most prevalent spelling variety.

Also, in terms of Clang direction, would it be acceptable to continue to add target-specific keywords for each new feature that compilers must interpret for correctness, and handle them directly in things like the parser case statements? Or should there be a more generic way of handling this, similar to Attr.td?

I think we can probably tablegen more than we do already (for example, it would be nice for TokenKinds.def and Attr.td to be hooked together so you don't need to specify the spelling of the keyword in two places), but I'm not confident we can make it as general as attributes given the parsing implications. The closest idea I had would be to encode some kind of "this is a decl specifier" vs "this is a type specifier" vs "this is written on the declarator" kind of information in Attr.td, have some generic code in the parser that says "now parse keyword attributes in <whatever syntactic location>" that dispatches to some tablegenerated code which eats the correct tokens. However, I would expect this to be somewhat inflexible for parsing (e.g., ordering between keywords and other syntax) and maybe not suitable for all keyword attributes (parameterized keywords, for example). But if we started with the use cases we have, maybe it would generalize well enough to be worth it?

For reference, in terms of kinds of existing keywords in Attr.td, we have:

Decl: 8
Stmt: 0
Type: 16
Decl Or Stmt: 2
Decl Or Type: 8
Ignored: 1

So there's a reasonable variety of cases to have to think about for a generalized solution, but we could perhaps tackle them one at a time (start with type attributes because they're so popular and then add declaration attributes later and have more parsing locations).

Thanks @aaron.ballman for the feedback about spellings. I've gone with the lower-case names like __arm_streaming in what follows, as a plausible strawman.

My main concern with keywords is:

This approach means there's plenty of flexibility available to parse the keyword where you think it makes sense.

If everyone parses keywords where it makes sense to them personally, for their specific use case, I'm worried that we'll end up with a lot of slightly different rules. (This tended to happen for pre-standard features, and like you say, is still the case for GNU attribute handling between Clang and GCC, given GCC's poorly-defined rules.)

For example, a type property like __arm_streaming only applies to functions, so it wouldn't make sense for bespoke rules to allow the keyword in tagged types or in array types. If a property only applies to object types then it wouldn't make sense for bespoke rules to allow the keyword after a parameter list.

So I think it makes sense to try to make the SME attributes an instance of a (new) generic solution. I think we want something “like standard attributes, but for semantic information”. That might sound like wanting something “like ice, but not cold”. But a lot of valuable work went into defining the parsing rules for standard attributes, and defining what they appertain to. It seems like a good idea to reuse those parts rather than reinvent the wheel.

https://reviews.llvm.org/D139028 is an RFC for adding “attribute-like keywords”: keywords that can appear exactly where a standard attribute can appear. This means that the keywords are syntactically valid in far more places than necessary for this initial use case, but it provides a simple and mechanical rule. And the keywords would have to be rejected in those additional places anyway, even if we don't parse them as attributes.

The covering note has a lot more justification (too much, sorry!). Does this look like a possible way forward?

Previously I was arguing in favour of the decl-to-type sliding rule, because:

From discussions I've had with people who will write/are writing SME code, the distinction between arm_locally_streamng being a property of the definition and arm_streaming being a property of the type seems contrived to them, especially when the usage of SME is private to a single translation unit.

Taking the RFC approach involves abandoning that and sticking strictly to the standard appurtenance rules. But hopefully it's a reasonable trade-off. The sliding rule is clearly going against the direction of travel anyway, so I'm probably being too Luddite there.

sdesmalen updated this revision to Diff 482440.Dec 13 2022, 5:44 AM

Changed arm_new_za to be a declaration attribute instead of a type attribute (this was something that @rsandifo-arm pointed out to me recently)

Thanks @aaron.ballman for the feedback about spellings. I've gone with the lower-case names like __arm_streaming in what follows, as a plausible strawman.

My main concern with keywords is:

This approach means there's plenty of flexibility available to parse the keyword where you think it makes sense.

If everyone parses keywords where it makes sense to them personally, for their specific use case, I'm worried that we'll end up with a lot of slightly different rules. (This tended to happen for pre-standard features, and like you say, is still the case for GNU attribute handling between Clang and GCC, given GCC's poorly-defined rules.)

Ah, I meant more that we have flexibility on what we want the grammar of the extension to be. If other implementations wanted to pick up the functionality, hopefully they'd pick up the same grammar as well.

For example, a type property like __arm_streaming only applies to functions, so it wouldn't make sense for bespoke rules to allow the keyword in tagged types or in array types. If a property only applies to object types then it wouldn't make sense for bespoke rules to allow the keyword after a parameter list.

Agreed, but that's a semantic property of the attribute rather than a syntactic property. We have control over both, of course, because this is our extension and we can define it how we like. But I was expecting we'd define a syntactic location to parse the attribute and we'd handle appertainance issues in SemaDeclAttr.cpp when deciding whether to convert the parsed attribute into a semantic attribute or not.

So I think it makes sense to try to make the SME attributes an instance of a (new) generic solution. I think we want something “like standard attributes, but for semantic information”. That might sound like wanting something “like ice, but not cold”. But a lot of valuable work went into defining the parsing rules for standard attributes, and defining what they appertain to. It seems like a good idea to reuse those parts rather than reinvent the wheel.

https://reviews.llvm.org/D139028 is an RFC for adding “attribute-like keywords”: keywords that can appear exactly where a standard attribute can appear. This means that the keywords are syntactically valid in far more places than necessary for this initial use case, but it provides a simple and mechanical rule. And the keywords would have to be rejected in those additional places anyway, even if we don't parse them as attributes.

FWIW, we already have a number of attributes implemented via a keyword today: ConstInitAttr, C11NoReturnAttr, a ton of CUDA and OpenCL keywords, calling conventions like __stdcall, etc. I'll take a look at the other patch to see what it's all about, but currently all keyword attributes need explicit parsing support added for them, as in: https://github.com/llvm/llvm-project/blob/main/clang/lib/Parse/ParseDecl.cpp#L4005 and https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaDecl.cpp#L7767.

The covering note has a lot more justification (too much, sorry!). Does this look like a possible way forward?

Previously I was arguing in favour of the decl-to-type sliding rule, because:

From discussions I've had with people who will write/are writing SME code, the distinction between arm_locally_streamng being a property of the definition and arm_streaming being a property of the type seems contrived to them, especially when the usage of SME is private to a single translation unit.

Taking the RFC approach involves abandoning that and sticking strictly to the standard appurtenance rules. But hopefully it's a reasonable trade-off. The sliding rule is clearly going against the direction of travel anyway, so I'm probably being too Luddite there.

Err, I'm struggling to see why the distinction is contrived. If the attribute applies to the type, then you get type-based semantics like the ability to overload or specialize on the presence/absence of the attribute -- you shouldn't get that behavior from a declaration attribute. e.g., if you use a declaration attribute on a function definition and then redefine the function without the attribute, that should give a redefinition error because that's two functions with the same name and same type being defined.

Thanks @aaron.ballman for the feedback about spellings. I've gone with the lower-case names like __arm_streaming in what follows, as a plausible strawman.

My main concern with keywords is:

This approach means there's plenty of flexibility available to parse the keyword where you think it makes sense.

If everyone parses keywords where it makes sense to them personally, for their specific use case, I'm worried that we'll end up with a lot of slightly different rules. (This tended to happen for pre-standard features, and like you say, is still the case for GNU attribute handling between Clang and GCC, given GCC's poorly-defined rules.)

Ah, I meant more that we have flexibility on what we want the grammar of the extension to be. If other implementations wanted to pick up the functionality, hopefully they'd pick up the same grammar as well.

Yeah, that's also how I was interpreting your comment. But my point is that, as the definers of this extension, we didn't really come into this needing/wanting custom grammar rules. The grammar for standard attributes would have been fine for us. We can't use those because standard attributes aren't allowed to affect semantics, but that's a rule imposed on us by the standard rather than a rule that we're imposing on ourselves. The grammar rules and the appurtenance rules would have been fine.

In other words, I agree that we have the flexibility to define specific rules for this extension that do whatever we want. But I don't think the extension is unusual enough to need that flexibility. There doesn't seem to be anything about the semantic function type properties in this extension that would require different grammar rules from other semantic function type properties. It seems better to have a generic way of attaching target-specific semantic information to types (and objects), just like the standard provides a generic way of attaching target-specific non-semantic information.

For example, a type property like __arm_streaming only applies to functions, so it wouldn't make sense for bespoke rules to allow the keyword in tagged types or in array types. If a property only applies to object types then it wouldn't make sense for bespoke rules to allow the keyword after a parameter list.

Agreed, but that's a semantic property of the attribute rather than a syntactic property. We have control over both, of course, because this is our extension and we can define it how we like. But I was expecting we'd define a syntactic location to parse the attribute and we'd handle appertainance issues in SemaDeclAttr.cpp when deciding whether to convert the parsed attribute into a semantic attribute or not.

Like you say, I think we're in agreement here, but approaching it from different angles. What I meant is that, if we add grammar rules that apply only to the keywords defined in this extension, those grammar rules would probably not allow the keyword to appear in things like tagged types or array types, because they would never be needed there. If each extension defines its own grammar rules, those rules would generally not allow keywords to appear in places where the keywords are always semantically invalid (although see below). This reduces the extent to which bespoke grammar rules for one extension can be cut-&-pasted into another extension.

Having grammar rules that are defined robotically based on the standard attribute grammar rules, rather than trying to tailor the grammar rules specifically to SME, should make it much more likely that the same grammar rules can be reused by later extensions. Doing that moves all of the extension-specific constraint checking to semantic analysis, rather than splitting it between parsing and semantic analysis.

So I think it makes sense to try to make the SME attributes an instance of a (new) generic solution. I think we want something “like standard attributes, but for semantic information”. That might sound like wanting something “like ice, but not cold”. But a lot of valuable work went into defining the parsing rules for standard attributes, and defining what they appertain to. It seems like a good idea to reuse those parts rather than reinvent the wheel.

https://reviews.llvm.org/D139028 is an RFC for adding “attribute-like keywords”: keywords that can appear exactly where a standard attribute can appear. This means that the keywords are syntactically valid in far more places than necessary for this initial use case, but it provides a simple and mechanical rule. And the keywords would have to be rejected in those additional places anyway, even if we don't parse them as attributes.

FWIW, we already have a number of attributes implemented via a keyword today: ConstInitAttr, C11NoReturnAttr, a ton of CUDA and OpenCL keywords, calling conventions like __stdcall, etc. I'll take a look at the other patch to see what it's all about, but currently all keyword attributes need explicit parsing support added for them, as in: https://github.com/llvm/llvm-project/blob/main/clang/lib/Parse/ParseDecl.cpp#L4005 and https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaDecl.cpp#L7767.

Thanks for looking at the patch. And yeah, I've seen those other keywords, but I think they show examples of the points I was making above. For example, we have:

// Microsoft compatibility
case tok::kw___cdecl:      // struct foo {...} __cdecl      x;
case tok::kw___fastcall:   // struct foo {...} __fastcall   x;
case tok::kw___stdcall:    // struct foo {...} __stdcall    x;
case tok::kw___thiscall:   // struct foo {...} __thiscall   x;
case tok::kw___vectorcall: // struct foo {...} __vectorcall x;
  // We will diagnose these calling-convention specifiers on non-function
  // declarations later, so claim they are valid after a type specifier.
  return getLangOpts().MicrosoftExt;

This seems like a case where Microsoft defined grammar rules that allow the keywords in more places than strictly necessary, so that the job of rejecting them moves from parsing to semantic analysis. The grammar rules could instead have rejected the keywords in these locations. Both ways work, and as far as I can tell, there are no particular differences between the Microsoft extensions and the SME extensions that would suggest the SME extensions should take a different approach. So we could accept the SME keywords in the same places “for consistency”, or we could reject them on the basis that the keywords are never needed in that context (just like they aren't needed in that context for the Microsoft extension). The choice seems pretty arbitrary.

I think there's a danger that adding SME in a similar ad hoc way would increase the sense of arbitrariness for whoever adds the next similar extension. :-)

The covering note has a lot more justification (too much, sorry!). Does this look like a possible way forward?

Previously I was arguing in favour of the decl-to-type sliding rule, because:

From discussions I've had with people who will write/are writing SME code, the distinction between arm_locally_streamng being a property of the definition and arm_streaming being a property of the type seems contrived to them, especially when the usage of SME is private to a single translation unit.

Taking the RFC approach involves abandoning that and sticking strictly to the standard appurtenance rules. But hopefully it's a reasonable trade-off. The sliding rule is clearly going against the direction of travel anyway, so I'm probably being too Luddite there.

Err, I'm struggling to see why the distinction is contrived. If the attribute applies to the type, then you get type-based semantics like the ability to overload or specialize on the presence/absence of the attribute -- you shouldn't get that behavior from a declaration attribute. e.g., if you use a declaration attribute on a function definition and then redefine the function without the attribute, that should give a redefinition error because that's two functions with the same name and same type being defined.

Yeah, I agree it's not contrived from a compiler developer's point of view, or from the point of view of someone with a specific interesting in language semantics. But the users I talked to are experts in other fields who just use C++ as a tool. The common use case for them was to write a function with certain properties and then call it. From that basic use case, it doesn't matter (to them) whether certain properties appertain to the types and others to the declaration.

The difference would start to matter to them if they defined virtual functions (for example), or exported the SME-specific functions as an API. But that wasn't the use case they were considering.

Gentle ping on this discussion. @aaron.ballman

So I still don't see this doing the module writing/reading part, which is necessary if you're making this part of the type.

I'd like to see more testing/thought put into how this interfaces with overloads and template resolution.

Finally, and this is something @aaron.ballman probably wants to answer: Is this sufficiently important that we're willing to take the additional overhead of 8 bits for each function type (plus an extra 8 bits for its prototype?). Memory wise, this seems a little costly.

clang/include/clang/AST/Type.h
3940

So its not just ast-dump that matters, it is what happens when these functions are exported from a module, and imported from another? Unless you make changes to those areas, this SME stuff will be lost, since you're making it part of the type.

clang/test/Sema/aarch64-sme-func-attrs.c
173

What happens with implicit instantiations? Can you make sure calling these doesn't lose the attribute? Our implicit instantiation mechanism has an opt-in for a lot of attributes/etc.

Additionally, can these be overloaded on in C++? I don't see any tests for this. Since they are part of the function type, is that deduced properly? (that is, a function pointer passed as template argument, does it pick up the right type, and does it end up as a separate template isntantiation?).

Finally, and this is something @aaron.ballman probably wants to answer: Is this sufficiently important that we're willing to take the additional overhead of 8 bits for each function type (plus an extra 8 bits for its prototype?). Memory wise, this seems a little costly.

It'd be nice to have a rough idea of what the overhead costs are. I think we can steal a few bits back to reduce memory overhead if necessary.

clang/include/clang/AST/Type.h
4123

We could steal two bits back here and make this a 6-bit bit-field (we'd probably want to adjust SME_AttributeMask accordingly as well).

clang/include/clang/Basic/AttrDocs.td
6322

Still wondering on this.

clang/lib/Sema/SemaDecl.cpp
3746–3753

Yup, that's the scenario I was wondering about -- thanks for the answer (and I see test coverage for the situation as well).

Finally, and this is something @aaron.ballman probably wants to answer: Is this sufficiently important that we're willing to take the additional overhead of 8 bits for each function type (plus an extra 8 bits for its prototype?). Memory wise, this seems a little costly.

It'd be nice to have a rough idea of what the overhead costs are. I think we can steal a few bits back to reduce memory overhead if necessary.

I just have knowing how much memory per-function-type, which is 2 bytes per at the moment it looks (1 for the function type, 1 for the prototype). Reducing it to 6 bits saves us 1 of those bytes I think, but the one on FunctionTypeExtraBitfields doesn't save us anything. That said... its entirely possible to make it cost nothing there now that I look... It is align-as void*, so align 4 or 8 depending on the platform. It currently contains a single unsigned (so 4 bytes) for the number of types in an exception spec. So on 64 bit systems, we currently don't use any additional memory there as far as I can tell, but on 32 bit systems this likely costs 32 bits there (despite being an 8 bit bitfield).

By standard imp-limits, apparently we only need to handle 8 bits worth for the Exception Spec types. I'd be tempted, based on exception specs being deprecated and rarely used, to reduce it to an 'unsigned short' (2 bytes), which makes this cost-free on 32 bit machines. WDYT?

clang/include/clang/AST/Type.h
4123

That would be nice at least here, since Variadic/TrailingReturn would use the other 2, and mean that htis takes zero more data, instead of an entire additional byte.

By standard imp-limits, apparently we only need to handle 8 bits worth for the Exception Spec types. I'd be tempted, based on exception specs being deprecated and rarely used, to reduce it to an 'unsigned short' (2 bytes), which makes this cost-free on 32 bit machines. WDYT?

It's a bit orthogonal to this patch, but I think it's a reasonable change to make so long as we don't break significant code.

@sdesmalen @rsandifo-arm @aaron.ballman @erichkeane What is the plan for this patch? Is it going to be abandoned in favor of D139028, or can it be landed in its current form after addressing all outstanding review comments?

sdesmalen updated this revision to Diff 528366.Jun 5 2023, 3:32 AM
sdesmalen retitled this revision from [Clang][AArch64] Add ACLE attributes for SME. to [Clang][AArch64] Add/implement ACLE keywords for SME..
sdesmalen edited the summary of this revision. (Show Details)
  • Rebased patch after patch to add RegularKeyword attributes (D148702) landed
  • Split off the request to reduce the size of FunctionTypeExtraBitfields (and use alignment of the struct to pointer type) to a new patch: D152140
  • Added test to ensure that the serialization of type attributes is exported/imported to/from modules, see clang/test/Modules/aarch64-sme-keywords.cppm
  • Added some new tests for template instantiation and function overloading with SME attributes:
void normal_func(void) {}
void streaming_func(void) __arm_streaming {}

template<typename T> int test_templated_f(T);
template<> constexpr int test_templated_f<void(*)(void)>(void(*)(void)) { return 1; }
template<> constexpr int test_templated_f<void(*)(void)__arm_streaming>(void(*)(void)__arm_streaming) { return 2; }

static_assert(test_templated_f(&normal_func) == 1, "Instantiated to wrong function");
static_assert(test_templated_f(&streaming_func) == 2, "Instantiated to wrong function");

and

// expected-cpp-error@+2 {{'__arm_streaming' only applies to function types; type here is 'int'}}
// expected-error@+1 {{'__arm_streaming' only applies to function types; type here is 'int'}}
int invalid_type_for_attribute __arm_streaming;

// Test overloads
constexpr int overload(void f(void)) { return 1; }
constexpr int overload(void f(void) __arm_streaming) { return 2; }
static_assert(overload(&normal_func) == 1, "Overloaded to wrong function");
static_assert(overload(&streaming_func) == 2, "Overloaded to wrong function");
sdesmalen updated this revision to Diff 528376.Jun 5 2023, 4:39 AM
sdesmalen marked 15 inline comments as done.
  • Use only 6 bits for AArch64SMEAttributes to reduce the size of ExtProtoInfo.
  • Added test for implicit instantiation.
sdesmalen marked 4 inline comments as done.Jun 5 2023, 4:42 AM
sdesmalen added inline comments.
clang/include/clang/AST/Type.h
3940

Hi @erichkeane I've added a test for module export/import and was surprised to see this work without any additional changes. Do you think the added test is correct/sufficient to show that this works?

clang/include/clang/Basic/AttrDocs.td
6322

Sorry I missed that comment earlier. This is indeed intentional, there is no trailing __ after __ARM_FEATURE_SME.
The macro is defined here: https://github.com/ARM-software/acle/blob/main/main/acle.md#scalable-matrix-extension-sme

clang/test/Sema/aarch64-sme-func-attrs.c
173

I've added a few tests for these cases!

erichkeane added inline comments.
clang/include/clang/AST/Type.h
3940

I don't have a good idea of that, I know little about our modules implementation. Perhaps @ChuanqiXu knows?

We ended up getting the regular-attribute-like-keywords patch in a different form. Do we still want to do this as is? Does it need rebasing?

sdesmalen marked an inline comment as done.Jun 5 2023, 6:23 AM

We ended up getting the regular-attribute-like-keywords patch in a different form. Do we still want to do this as is? Does it need rebasing?

I've rebased this patch on top of @rsandifo-arm's patches so that it uses the attribute-like-keywords now (note for example that the tests have been updated to use the new keywords).

D148700 and D148702 only addressed the parsing side of things, not the semantics. This patch adds the remaining attributes/keywords (D148700 only added __arm_streaming), integrates the type information into the AST and adds codegen to the LLVM IR function- and call-site attributes.

I think we need a few tests to ensure that these are properly propagated on function templates (both for Sema and codegen). Also, a handful of nits (See my list).

clang/include/clang/AST/Type.h
3958
clang/include/clang/Basic/Attr.td
2459

So, why are __arm_shared_za and __arm_preserves_za different tenses, despite being very similar sounding? Should it be __arm_shares_za (or alternatively, __arm_preserved_za)?

clang/include/clang/Basic/AttrDocs.td
6595

I'm missing a touch of context that an additional word or two might help. is this 'the function requires the 'target' processor has SME? or the 'runtime' processor?

Also, the rest of the list ends in a '.', but this one doesnt.

6633

"ZA" should be defined 1st time it is used in this doc, and the ones below.

6635

Same comments as above.

6679

Same here.

6707

again.

clang/lib/Sema/SemaDecl.cpp
3758
clang/lib/Sema/SemaDeclAttr.cpp
9444

This switch should ONLY be 'handle' function calls. Please break this off into its own function.

sdesmalen updated this revision to Diff 536260.Jun 30 2023, 8:17 AM
sdesmalen marked 11 inline comments as done.

Updated patch:

  • Addressed review comments (clarified documentation, minor refactoring)
  • Added new tests (codegen + sema tests to make sure attributes are propagated to types when using variadic templates). If there's any other specific tests you'd like me to add, let me know.

I waited a bit to update this patch, because I wasn't sure if the syntax might still change pending the conversation on D148700, but at the moment the status is that the keywords have a specific benefit over C++ style attributes in that they are not silently ignored. If the concensus on this changes we can easily support the other syntax through another patch. I hope this patch with semantic support can land if everyone is happy with the implementation. I think I've addressed all outstanding comments now.

clang/include/clang/AST/Type.h
3940

Gentle ping @ChuanqiXu

Do you know if this patch requires any further changes for reading/writing modules? The test I've added seems to work fine.

clang/include/clang/Basic/Attr.td
2459

I think this was mostly for historical reasons. @rsandifo-arm mentioned to me that he was still open to renaming these.

Gentle ping @aaron.ballman and @erichkeane. I've addressed all comments and suggestions and believe we found agreement on the approach (to use a keyword instead of an attribute).
Are you happy to accept the patch?

Mostly LGTM from a spec POV, but some comments below.

clang/include/clang/Basic/Attr.td
2439

In current main, this attribute is gated on TargetAArch64 rather than TargetAArch64SME. The reason is that, from a QoI point of view, I don't think we should require the +sme extension in order to accept an __arm_streaming type. +sme is instead required to compile a function with __arm_streaming type, or a call to a function with __arm_streaming type. IMO those are more CodeGen rather than Sema restrictions.

E.g., if a header file contains

void sme_foo(void) __arm_streaming;

then an __attribute__((target_version("sme"))) function should be able to call it. But we shouldn't require +sme to be passed on the command line in order to compile the declaration of sme_foo.

I think the same principle applies to the other function-type attributes. In particular, __arm_preserves_za functions and __arm_streaming_compatible functions do not require SME to be present.

I think we should also allow:

__arm_locally_streaming void __attribute__((target_version("sme"))) ifunc() { … }

since __arm_locally_streaming does not change the function signature.

So I think all of these attributes should be gated on TargetAArch64, with checks for SME made elsewhere where necessary.

(Unfortunately, since command-line options are out of scope for ACLE, I don't think it can really specify this.)

clang/include/clang/Basic/AttrDocs.td
6588

The bullet was trying to describe the function's ABI. I don't think “target” is appropriate in an ABI context (where you might be describing a binary that already exists, rather than a compilation). So I've a slight preference for dropping “target”.

I see in another comment, Erich made a distinction between “target” and “run-time”. If we do use one or the other, I think “run-time” is more correct.

“target” is right for __arm_locally_streaming and __arm_new_za though.

6623

How about “non-streaming” rather than “normal”?

6626
6666

The other type attributes also only describe normal returns.

6738

Just trying to avoid “if available”, since the saves aren't being provided for the benefit of anyone else. Feel free to reword to something else.

clang/include/clang/Basic/DiagnosticSemaKinds.td
3608–3609
clang/lib/AST/Type.cpp
3559

This comment probably needs updating for the new field, although the comment already seems pretty out of date. (It looks like the separate bool was removed in 2011.)

I suppose the new field doesn't create any ambiguities because it's always present. But given the comment about performance sensitivity, I wonder if we should combine the new field with the trailing return flag?

clang/lib/AST/TypePrinter.cpp
943

Are the !InsideCCAttribute conditions necessary? AIUI, InsideCCAttribute exists to suppress the default calling convention when printing the type to which an explicit CC is added. That doesn't apply to SME attributes, because they aren't modelled as traditional CCs, and because the default/normal case doesn't have any syntax associated with it.

clang/lib/CodeGen/CodeGenModule.cpp
2291

How about something like:

// Handle SME attributes that apply to function definitions,
// rather than to function prototypes.

I can see why the current comment helped when we were still using GNU attributes, due to the way that type attributes “slid” from the declaration to the type. But now that the attributes are non-sliding type attributes, it wouldn't make sense to query D->hasAttr for them.

The suggested change also avoids the contradiction between “we only need to” and “the same holds for”.

clang/lib/Sema/SemaExpr.cpp
9688

The meaning of “from” and “to” seem to be the opposite of what I'd initially assumed. For casts, I'd have expected the type of the object that is being casted to have FromType and the cast type (the type of the result) to be ToType. Dropping ZA would then mean that FromAttributes has __arm_preserves_za and ToType doesn't.

I think that also makes sense for the virtual function check above. The overriding function has FromType and the function it overrides has ToType. We need to be able to convert from FromType to ToType (e.g. to initialise the vtable), but don't need to be able to go the other way.

clang/test/Sema/aarch64-sme-func-attrs.c
58

Is it not possible to address this FIXME yet? Might be worth expanding the comment to say why if so.

185

I think it'd be worth having a few more of these, in particular testing that an override can be __arm_shared_za __arm_preserves_za if it wants to be.

Gentle ping @aaron.ballman and @erichkeane. I've addressed all comments and suggestions and believe we found agreement on the approach (to use a keyword instead of an attribute).
Are you happy to accept the patch?

I think things are looking close, still some minor issues to correct though.

clang/include/clang/AST/Type.h
3987

I think this should also be an unsigned bit-field so that it packs together with the new field added below. Otherwise we're liable to end up with padding between this field and the next bit-field (which will take a full allocation unit anyway). How about unsigned NumExceptionType : 10;? We're reducing the count by 6 bits, but.... I struggle to imagine code being impacted and we're still allowing more than the implementation limits require.

4172–4174

IIRC, at least one of the compilers used to build Clang has issues with adjacent bit-fields of differing types, so we'd usually make these bool fields be unsigned at this point to ensure they pack together properly.

4204–4205

Isn't the more idiomatic form of this written as AArch64SMEAttributes &= ~Kind;?

clang/include/clang/Basic/AttrDocs.td
6623

non-streaming sounds good to me.

clang/include/clang/Basic/DiagnosticSemaKinds.td
3608–3609

Also, remove the single quotes around %0 and %1 -- you're getting duplicate quotes in the test cases because of them.

clang/lib/Sema/SemaType.cpp
7937
sdesmalen updated this revision to Diff 546479.Aug 2 2023, 7:56 AM
sdesmalen marked 19 inline comments as done.
  • The attributes no longer require +sme to be passed.
  • Fixed IsInvalidSMECallConversion to consider the correct To/From types and added a corresponding test for the virtual override functions.
  • Created a new category for the Arm SME attributes in the Clang Attributes document.
  • Addressed all other comments.

Thanks both for the detailed review and latest round of comments, I've tried to address them all.

clang/include/clang/AST/Type.h
3987

That's what I did initially in the patch where I limited NumExceptionType to 16bits (D152140), but there was the preference to have this be a uint16_t instead. I've changed it back again to a bitfield though.

clang/include/clang/Basic/Attr.td
2439

That's a fair point. This change was partially made in D152141, so I've updated both patches to reflect the right behaviour.

clang/include/clang/Basic/AttrDocs.td
6318

@rsandifo-arm pointed out that I marked this comment as done, but hadn't done it. I've fixed that now, and created a new category for the SME extensions.

clang/include/clang/Basic/DiagnosticSemaKinds.td
3608–3609

Thanks! That explains the double quotes indeed.

clang/lib/AST/TypePrinter.cpp
943

You're right, they were not necessary! Thanks.

clang/lib/Sema/SemaExpr.cpp
9688

Thanks for pointing out, that was indeed wrong. I've fixed it, and added a test-case for the virtual function override for the __arm_preserves_za case.

clang/test/Sema/aarch64-sme-func-attrs.c
58

This was a stale comment, these cases are checked later on in section:

// 
// Check for incorrect conversions of function pointers with the attributes
//
aaron.ballman accepted this revision.Aug 2 2023, 9:17 AM

LGTM, though please give other active reviewers a bit of time to chime in before landing. Note, @erichkeane is out on sabbatical at the moment, so there's no need to wait for his LG (he can leave post-commit comments on the review when he gets back though, so keep your eyes peeled for that just in case).

clang/include/clang/AST/Type.h
3987

I'm sorry for the back and forth! I think Erich's suggestion was wrong in the other thread, mostly because of MSVC: https://godbolt.org/z/W9fn7Ghxo

This revision is now accepted and ready to land.Aug 2 2023, 9:17 AM

Thanks for dropping the attribute requirements down to TargetAArch64. But the offsetting requirement was that something somewhere (CodeGen?) must raise an error if code requires SME and SME isn't present. I think that means:

  1. SME is not enabled for an __arm_streaming or __arm_locally_streaming function definition.
  2. SME is not enabled for a call to an __arm_streaming function.
  3. SME is not enabled for an __arm_shared_za or __arm_new_za function definition.

(Calls to __arm_shared_za functions don't need to be listed explicitly, since only __arm_shared_za and __arm_new_za functions can make such calls.)

In all cases, the check would apply after __attribute__((target_version)) is taken into account.

Perhaps that's a separate patch though.

clang/include/clang/Basic/AttrDocs.td
6645
clang/test/Sema/aarch64-sme-func-attrs.c
187

Thanks, this is indeed the case I was suggesting to test. But my point was that it shouldn't be an error. It's OK for an override to be __arm_preserves_za even if the function it overrides isn't. Something that calls shared_za_member directly from an S2_2 object can take advantage of the extra guarantee.

It's the other way that's wrong. If a virtual function is __arm_preserves_za then any override must be too.

The principle is similar to covariant return types. E.g.:

struct S1 { virtual S1 *f(); };
struct S2 : S1 { S2 *f() override; }; // OK
struct S3 : S2 { S2 *f() override; }; // OK
struct S4 : S3 { S1 *f() override; }; // Error

An __arm_preserves_za function acts like functions that return S2 * in this example, and a non-__arm_preserves_za function acts like functions that return S1 *.

sdesmalen updated this revision to Diff 547696.Aug 7 2023, 3:25 AM

Added enum AArch64SMECallConversionKind to make it explicit when calling
IsInvalidSMECallConversion whether or not 'preserves_za' can be dropped
(e.g. in assignment), can be added (e.g. when overriding a parent method)
or must match exactly (e.g. when redefining a function).

I'll post a separate patch for guarding the SME attributes when not
compiling for a target with sme.

rsandifo-arm accepted this revision.Aug 7 2023, 6:53 AM

LGTM too, thanks.

This revision was landed with ongoing or failed builds.Aug 8 2023, 12:31 AM
This revision was automatically updated to reflect the committed changes.
dewen added a subscriber: dewen.Sep 5 2023, 8:47 PM