GCC supports attribute((leaf)) as an optimization hint described in:
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
This patch adds support for this attribute in Clang/LLVM.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
(To clarify, I am not opposing this, my comment before was brief but not meant to be negative. I'm generally for new attributes and this one is already in existing source so that is even better.)
There is an RFC going out with this prototype as reference. When there is consensus on the RFC, this will get in shape for landing with complete tests and all.
Great. I didn't find anything when I looked for leaf in my cfe-dev inbox. Could someone put a link here, maybe directly into the commit message as it will be useful even after this goes in. Thanks :)
I just sent the RFC with the subject line [RFC] Leaf Attribute Support in Clang/LLVM.
Yep, I'm writing my response there ;)
FWIW, http://lists.llvm.org/pipermail/llvm-dev/2020-October/146113.html
I'd recommend splitting this into two patches, one for LLVM changes and another for Clang changes.
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | Should this attribute also be supported on things like ObjC method decls or other function-like interfaces? | |
1454 | This should be referring to LeafDocs rather than undocumented. | |
clang/include/clang/Basic/AttrDocs.td | ||
3984 | as leaf -> with the leaf I'm not certain how to interpret that functions are not allowed to enter their caller's translation unit. I sort of read that as leaf functions are not allowed to call (or otherwise jump) out of the translation unit in which they're defined -- is that about right? | |
3985 | What sort of diagnostic checking should the user expect? e.g., can the user expect Clang to diagnose obviously incorrect code like: [[gnu::leaf]] void func(void (*fp)(void)) { fp(); // This seems like a bad thing } | |
3988 | We should probably link to the latest docs (https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html) rather than 4.7.2. |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3984 | I think the description is a little confusing. |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3984 | Ah, thank you for the explanation! How about: Functions marked with the ''leaf'' attribute are not allowed to jump back into the caller's translation unit, whether through invoking a callback function, a direct external function call, use of 'longjmp', or other means.? Is this property transitive? e.g., // TU1.c void func(void) { leaf(); } void bar(void) {} // TU2.c __attribute__((leaf)) void leaf(void) { baz(); // Is this not allowed? } // TU3.c void baz(void) { bar(); } The "directly call functions exported by the unit" makes me think the above code is fine but it could also be that "direct" in this case means "through a function designator rather than a function pointer". |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | Do I need to do anything else to support this attribute in Objective-C as well? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
That's already happening automatically -- there's a C and C++ spelling available for it and the attribute doesn't specify that it requires a particular language mode or target.
You can add multiple subjects to the list here, so you can have this apply to Function, ObjCMethod for both of those. Another one to consider is whether this attribute can be written on a block declaration (like a lambda, but with different syntax). Beyond that, it's mostly just documentation, devising the test cases to ensure the ObjC functionality behaves as expected, possibly some codegen changes, etc. |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3985 | This is a compiler hint provided by the user, and if the user misuses that attribute, it might result in undefined behavior. | |
3985 | I think this property is transitive. |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3985 | @aaron.ballman, your code example is not " obviously incorrect " and we should not diagnose that (see below). In fact, I doubt we can ever diagnose misuse except during the LTO linking stage, assuming the expected use case. That said, you could warn and ignore if a function definition is annotated. // library.c void bar(void) {} [[gnu::leaf]] void func(void (*fp)(void)) { fp(); // not necessarily a bad thing } // user.c #include <library.h> void test() { func(&bar); // perfectly fine. } |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3984 | I like your description, and I changed the doc that incorporates your feedback. |
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3985 |
Oh, thank you for that example!
Why would you want to warn and ignore in that situation? | |
3985 |
That makes sense to me! I think I'd recommend a slight modification to the docs then: Functions marked with the ``leaf`` attribute are not allowed to jump back into the caller's translation unit, whether through invoking a callback function, a direct, possibly transitive, external function call, use of ``longjmp``, or other means. The last question is: by "direct" does the GCC docs imply that calls back to the caller's TU through a function *pointer* are not UB? I'd imagine those would also be bad, but I'd like to make sure (and perhaps we can remove the direct from the wording if calls through function pointers are disallowed). |
The more I think about it, the more I think we should never create a leaf/nocallback definition. Only declarations should carry that attribute.
I'm also still not convinced nocallback is a good name. @efriedma @aqjune @fhahn @reames What are your thoughts on the name. My earlier idea was inaccesiblecodeonly, as it matches the inaccisblememonly idea. I'm not married to it, nocallback is just not great IMHO as direct calls back into the caller TU are also forbidden and there is already !callback.
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3985 |
Let me try to explain, sorry I didn't earlier. GCC says (emphasis added):
The thing is, it doesn't make sense to have a leaf definition. Leaf functions can always call stuff "in their own TU". |
When talking about the IR that gets lowered to LLVM, I think that's very reasonable. When talking about the frontend, I'm a bit less certain. GCC and ICC both do not warn when you apply the attribute solely to a definition, so one concern is with code portability where existing code is harder to port into Clang. However, when I tried to find examples of the attribute being written on a definition, I couldn't spot any so this may not be a particularly large concern.
Another potential concern is that a situation like this seems harmless and diagnosing the use on the definition seems user-unfriendly:
// In a header file __attribute__((leaf)) void func(void); // In a source file __attribute__((leaf)) void func(void) { ... }
The last concern is whether non-compiler-writing users will understand the behavior.
// #1 __attribute__((leaf)) void func(void) {} // Declares and defines a function at the same time // #2 __attribute__((leaf)) void func(void); void func(void) {} // Inherits the attribute from the previous declaration
I would suspect that users will see #1 and #2 as defining the same things and may be surprised if #1 is diagnosed but #2 is fine, but that's just a gut feeling without evidence backing it up.
I'm not insisting that we accept the attribute on a definition, but if we do want to make a distinction in the frontend between declaration and definition, we should document it with some explicit examples.
Hi,
Naming is a hard thing... I have no special preference. :/
However, I'd like to understand the details of this attribute.
Would LTO be affected because leaf is guaranteed to untouch the current translation unit only?
// a.c int x; void f1() { f2(); } void g() { x = 3; } // b.c void f2() { leaf(); } // leaf.c attribute((leaf)) void leaf() { g(); }
IIUC this program is okay because g() and the caller of leaf() are in different TUs.
But, let's assume that a.c and b.c are LTO-ed, and leaf.c is separately compiled.
If LTO merges a.c and b.c into the same module, the two TUs cannot be distinguished anymore; either leaf should be dropped, or LTO should somehow conceptually keep two TUs.
Would it be a valid concern? Then I think it should be mentioned.
Another question is more about the motivation of this attribute (well, I know it is introduced by gcc first; just throwing a question :) )
If the motivation is to support better data flow analysis, is there something special in callback itself?
The gcc document states that sin() is a leaf function, and IIUC this is because sin() never touches the memory allocated at caller's TU (because errno isn't at the caller's TU).
I think things are easier if we simply say that leaf cannot touch the memory of current TU, regardless of the existence of callbacks.
Is there something important in the callback itself?
As noted by the GCC docs, it doesn't mean anything on a definition so that you can safely merge TUs. I want us to forbid leaf on IR function definitions for that reason, it would not mean anything and be only confusing.
Another question is more about the motivation of this attribute (well, I know it is introduced by gcc first; just throwing a question :) )
If the motivation is to support better data flow analysis, is there something special in callback itself?
The gcc document states that sin() is a leaf function, and IIUC this is because sin() never touches the memory allocated at caller's TU (because errno isn't at the caller's TU).
No, that is not it. It is leaf because it will not transfer control to the callers TU.
I think things are easier if we simply say that leaf cannot touch the memory of current TU, regardless of the existence of callbacks.
Is there something important in the callback itself?
Not really, IMHO, leaf means you cannot transfer control to the callers TU without going threw the original call site (via return or throw).
It is not a memory thing. However, the "almost" matching memory property is called inaccesiblememonly so that is why I wanted to call this inaccessiblecodeonly.
We are totally fine to rename it to something else, and we do see the potential confusion with the existing callback attribute that we have.
If I understand correctly, inaccessiblememonly functions may only access memory that is not accessible by the current compilation unit (current compilation unit means the compilation unit that inaccessiblememonly function is defined).
If we use inaccesiblecodeonly, it implies that leaf functions may only access code that is not accessible by the compilation unit that they are defined.
But, leaf attribute does not enforce any rule for accessing code in the leaf attribute function's compilation.
It only enforces that leaf function does not access any code in its caller's translation unit.
That's is why I'm not sure whether inaccesiblecodeonly is a good name for that.
Please let me know if I'm missing anything about inaccessiblememonly attribute.
@jdoerfert what do you think about that?
clang/include/clang/Basic/AttrDocs.td | ||
---|---|---|
3985 | I think the idea is that the control-flow should never come back to the caller's translation unit by any kind of control-flow changing mechanism (direct/indirect and call/jump). |
The GCC documentation specifically gives the example of the standard C function qsort as one that does not qualify as __attribute__((leaf)) because it uses a callback function (that presumably might be from the caller's own TU). AIUI the claim the attribute makes is that no normal control flow (i.e. excluding only signal handlers) would call any function in the caller's TU by any means, nor longjmp to any state captured by a setjmp call in the caller's TU, and thus only normal return or normal C++ exception handling return/unwind would reach the caller's TU again after the jump (call) to this entry point. The caller is thus presumed not to share potentially-called functions with the callee by any means, whether direct symbol references or function pointers passed in the call or function pointers previously stored somewhere (e.g. a C++ vtable). The compiler can make whatever assumptions about potential dataflow/side-effect and the like are implied by this exclusion of TU-local code paths from the otherwise unknown call graph of the external call.
Okay, I see.
I agree that having this attribute at definitions is slightly dangerous..!
But I am not still 100% sure about the safety of merging... I see that it is okay when leaf is on a definition, but what about declaration?
// a.ll define void f1() { f2(); } define void g() { x = 3; } // b.ll define void f2() { leaf(); } declare leaf void @leaf() ; If @leaf() was actually calling @g(), is merging a.ll and b.ll valid?
It is not a memory thing. However, the "almost" matching memory property is called inaccesiblememonly so that is why I wanted to call this inaccessiblecodeonly.
Eh, actually my question was more like a question to gcc people about why leaf was defined that way. Maybe my question is not relevant in this thread. Thank you for answering though.
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | AFAIK, users can specify function attributes in lambda expressions. |
Leaf attribute is specifically intended for library functions and I think all the existing usage of leaf attribute is in the library function declarations.
For ex, it is only used in syscalls in Fuchsia.
Therefore, I'm not sure whether it is really necessary to ban leaf attribute in function definitions.
Even though function attributes are typically intended to be used in the function declaration, compilers do not have policy to forbid using them in the function definition.
Maybe it's because the leaf (or nocallback or whatever) attribute at the function definition in IR can't be used for any optimization?
For the callers, leaf attribute at the function declarations is already giving the information.
But the attribute at function definitions really give no additional information inside the TU, so it's redundant.
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
I always forget that you can do that for declaration attributes using GNU-style syntax...
Not necessarily, you could pass one across TU boundaries like a function pointer, for instance. e.g., // TU1.cpp void foo() { auto l = []() { ... }; bar(l); } // TU2.cpp void bar(auto func) { func(); } | |
clang/include/clang/Basic/AttrDocs.td | ||
3985 |
I had confused myself -- I think @jdoerfert answered my confusion in an earlier comment and I just wasn't thinking about that case hard enough. I think we want to drop the word "direct" from "external function call" because it doesn't matter whether the user says: void bar(void); // Assume calling this is bad bar(); and void bar(void); // Assume calling this is bad void (*fp)(void) = bar; fp(); |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
As I mentioned before, leaf attribute is specifically intended for library functions and I think all the existing usage of leaf attribute is in the library function declarations. For this reason, I think we do not need to support them for lambdas. Is that reasonable? | |
clang/include/clang/Basic/AttrDocs.td | ||
3985 |
| |
3985 | I removed the direct from the documentation. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
Is this considered a library function? struct S { void f(); // Is this a library function? void operator()(); // How about this? }; If the answer is "no", then I think we only need to support FunctionDecl and nothing else (not even ObjCMethodDecl, which is like a member function for ObjC). If the answer is "yes", then it's not clear to me whether lambdas should or should not be supported given that the attribute on the lambda expression is attached to the function call operator for the lambda declaration. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
I see your point @aaron.ballman. I would say the second one is not really a library function. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
I feel like either they both are or they both aren't, but it's a question of how this attribute is intended to be used.
I've come around to that approach, but FunctionDecl represents any declaration of a function, including a definition. So you'll probably want to add a new SubsetSubject in Attr.td to represent a function declaration that's not a definition (and we could potentially reuse that subject for a few other attributes that can't be written on a definition). You can use FunctionDecl::isThisDeclarationADefinition() to distinguish between declarations and definitions. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
Sorry for being vague, and please let me try to clarify that. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
Okay, I think I'm on the same page as you now -- this attribute is most frequently written on free functions (ones that are not class members). However, I don't see a reason to disallow the attribute on a class member function though, or am I misunderstanding something? (GCC and ICC both seem to allow it on a class member function.)
I don't think it's okay to *ban* use of this attribute on function definitions (e.g., we shouldn't reject the user's code) because that will make porting code more difficult, but I think diagnosing as a warning is reasonable.. This is what I think should happen: Let's drop the support for ObjCMethodDecl as that support can be added later if we find use cases that need it (this will make CodeGen easier in this patch). Let's use a custom subject so that the attribute can only be written on a function declaration (which will automatically include member functions) but continue to not pass ErrorDiag in the SubjectList (so that we continue to warn rather than err if the subject is a function definition). Let's not support blocks or lambdas unless a user comes up with use cases for it, but let's add tests to ensure that the behavior of the attribute on those is not harmful since the implicit methods generated for them may be a bit strange. For instance, the alias attribute cannot be written on a definition and yet: https://godbolt.org/z/vbbxKj To be clear -- I think the default behavior you get from the suggested SubjectList changes will be fine, but if it turns out that adding this attribute on a definition through a lambda causes harm (UB, crashes, etc) then we may have to put in extra effort to explicitly disallow it there. And then add plenty of Sema tests for all of this so we're explicitly testing the behaviors we care about. Does that sound reasonable to you? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
Your understanding is right. Technically, leaf attributes should be able to be used in methods as well.
It sounds great! I agree with the plan, and I'll upload the changes in that direction. |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | @aaron.ballman I just added a simple rule for function declarations only. def FunctionDeclOnly : SubsetSubject<Function, [{!S->isThisDeclarationADefinition()}], "function declaration only">; I used that one in the leaf attribute definition: def Leaf : InheritableAttr { let Spellings = [GCC<"leaf">]; let Subjects = SubjectList<[FunctionDeclOnly]>; let Documentation = [LeafDocs]; let SimpleHandler = 1; } I thought that this will be straightforward, but after testing it on the following definition, surprisingly I did not get a warning. __attribute__((leaf)) void f() { } After some debugging, I think this is what's happening: /// Note: the function declaration does not become a definition until the /// parser reaches the definition, if called before, this function will return /// `false`. Do you have any suggestions? Is there anything that I'm missing? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | @aaron.ballman did you have a chance to take a look at my comment? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | Sorry about the delay in getting back to you on this (holiday schedule + C standards meetings got in the way of doing some reviews). Ugh, that's a good point -- we've not seen the function body by the time we're processing attributes, so we don't know whether to diagnose or not. I can think of two ways forward: 0) Not diagnose when the attribute is written on a function definition.
[[gnu::leaf]] void func(void); void func(void) { } // hasAttr<LeafAttr> on this will return true I'm pretty sure that Attr::isInherited() reports whether the attribute should be inherited, not whether it actually has been inherited, so #1 could be tricky. Personally, I'm fine with #0 if it turns out that #1 is painful. @jdoerfert, are you okay with that? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
Would that be ok if we land this patch without diagnosing a warning, and work on a diagnosis patch later? |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 | I'd be fine with that! |
clang/include/clang/Basic/Attr.td | ||
---|---|---|
1453 |
@aaron.ballman would you please approve the patch so I can merge it to the master then? |
Aside from a testing request, the frontend parts LGTM. I don't know enough about the LLVM side to feel comfortable signing off on it. @jdoerfert, would you mind approving that part?
clang/test/Sema/attr-leaf.c | ||
---|---|---|
5 | Can you also add tests that show the attribute diagnoses being written on the wrong subject or when given arguments? Also, can you add a test showing the attribute on a function definition with a FIXME comment about wanting to diagnose that case? |
clang/test/Sema/attr-leaf.c | ||
---|---|---|
5 | I added three more test cases which also includes a test case with FXME comment for function definition diagnosis. |
Thanks for the new tests!
clang/test/Sema/attr-leaf.c | ||
---|---|---|
13 | Can you add the newline to the end of the file? |
Apologies for being silent for too long.
This is missing a lang ref entry for nocallback and the attributes.ll test is arguably broken (see below).
The "definition" in llvm/include/llvm/IR/Attributes.td (see below), does not match the the behavior of clang/test/CodeGen/attr-leaf.c.
As I mentioned before, this doesn't have a meaning on definitions and that needs to be captured in the semantics (and preferably the FE).
/// Function cannot enter into caller's translation unit.
llvm/test/Bitcode/attributes.ll | ||
---|---|---|
408 | I kinda doubt this is going to work like it is supposed to. |
This is missing a lang ref entry for nocallback and the attributes.ll test is arguably broken (see below).
Could you please elaborate on missing lang ref entry? Where that should be added?
The "definition" in llvm/include/llvm/IR/Attributes.td (see below), does not match the the behavior of clang/test/CodeGen/attr-leaf.c.
As I mentioned before, this doesn't have a meaning on definitions and that needs to be captured in the semantics (and preferably the FE).
In that test case, leaf attribute is on the declaration.
What kind of a test case do you suggest to add?
Sure, my bad. Each enum attribute needs an entry in the language reference to define it's meaning, see also https://llvm.org/docs/LangRef.html#function-attributes
The content is in llvm/docs/LangRef.rst.
The "definition" in llvm/include/llvm/IR/Attributes.td (see below), does not match the the behavior of clang/test/CodeGen/attr-leaf.c.
As I mentioned before, this doesn't have a meaning on definitions and that needs to be captured in the semantics (and preferably the FE).In that test case, leaf attribute is on the declaration.
What kind of a test case do you suggest to add?
We need to first define the meaning of nocallback in the IR, without that it is hard to do anything else.
The problem in llvm/test/Bitcode/attributes.ll is clear?
This breaks llvm/test/Bitcode/attributes.ll.
The added test code was obviously wrong.
The problem in llvm/test/Bitcode/attributes.ll is clear?
@xur Instead of define void @f70() nocallback, it should be define void @f69() nocallback, right?
yes. Also #43 should be #42
I fixed it in
https://reviews.llvm.org/D92493
But that was just to make my test pass.
I fixed it in
https://reviews.llvm.org/D92493
But that was just to make my test pass.
Ok, thank you!
I did not realize that typo after the merge, and sorry about that!
I fixed the typo that I introduced in https://reviews.llvm.org/D93420.
Since this patch has been merged and the issue in the test has been resolved, I'm closing this review.
Could you please add a description of this attribute to LangRef?
We need the semantics of this.
Thank you!
Since the patch author has been ignoring my ask (and is active), and since documenting IR features is of paramount importance, I suggest we revert this patch. I think 3 weeks to document something is sufficient, and the author didn't provide an ETA.
I would like to ask everyone to reject all future patches that change the IR and don't include documentation, as experience says that we'll get miscompilations due to different people having different ideas of the semantics.
TL;DR: I'll revert this patch on Friday if no one chimes in. Thank you!
Thanks!
I'm fine with a reasonable ETA, but we really need all IR features documented to avoid bugs.
The urgency was not clear to me at the beginning, and I already added this to my list.
I did not have a chance to respond to your comment this week because of traveling.
But again, I did not mean to ignore this.
Was there any follow up? It seems that this hint (attribute) is currently not used by LLVM passes.
Should this attribute also be supported on things like ObjC method decls or other function-like interfaces?