-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Too strong validity checks for [[clang::musttail]] #54964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
cc @haberman Maybe Clang should just drop signature check? |
In general, we need to restrict the cases where we musttail: C calling convention rules limit the stack space available for tail calls, so we can't tail-call in the general case using the normal C calling convention. To ensure we have enough stack space, and that we can satisfy any other unusual conditions, we check that the signature strictly matches. This is why both clang and LLVM IR restrict musttail to those cases. I guess there are a few things we could do:
|
Yes, in general the rules around When I implemented Note that even the existing Loosening the rules would probably create more cases where we see these kinds of crashes. In other words, the existing attribute is already not fully portable. |
but there is no such ‘promise’ in https://clang.llvm.org/docs/AttributeReference.html and users may not need this additional assurance, which sadly, restricts this feature. Maybe we could have musttail and portable_musttail or introduce optional argument to musttail like ´__nonportable__’. I linked above link to CTRE project where they would like to use this feature but they cannot (and they consider current behaviour rather strange, and I agree). |
This is right - best effort feature, but no strict promises, so some obvious cases could be diagnosed by Clang and anything else by backend IMHO. |
Here is another example where I suspect the check is too stringent in a similar way as the original example: struct Foo {
__attribute__((noinline)) int foo(int) { return 0; }
};
struct Bar {
Foo foo;
int bar(int);
};
int Bar::bar(const int x) {
return foo.foo(x);
} A tail call works just fine (compiler explorer) on the targets I've checked. For example, x86-64: Bar::bar(int): # @Bar::bar(int)
jmp Foo::foo(int) # TAILCALL But adding Personally I would like the semantics "give me an error if this can't be a tail call on this target", not the semantics "give me an error if this can't be a tail call on all targets clang supports". I suspect most people would be looking for the former, since most people care about nearly none of the supported targets by volume. |
Strong +1, I would love such behaviour as well! So probably we need to come up with new attribute which would implement such behaviour. |
Proposal for [[clang::nonportable_musttail]] |
I'm just some random guy, so feel free to ignore me, but I would like to see some more tests for that attribute. In particular, I want some tests that it properly errors out if asked for something impossible, and doesn't (for example) ICE, as in haberman's example https://bugs.llvm.org/show_bug.cgi?id=51416. int a(int a1, int a2);
int b(int a1)
{
[[clang::nonportable_musttail]]
return a(1, a1); // legal, all args are in regs
}
int c(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10);
int d(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)
{
[[clang::nonportable_musttail]]
return c(a1, a2, a3, a4, a5, a6, a7, a8, a9, 1); // expected-error {{can't push an extra argument onto a stack frame that's not ours}}
}
int e(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10)
{
[[clang::nonportable_musttail]]
return c(1, a1, 3, a4, a5, a6, a7, a8, a9, a10); // legal, only register arguments are changed
}
int f(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10)
{
[[clang::nonportable_musttail]]
return c(a1, a2, a3, a4, a5, a6, a7, 1, 2, 3); // legal, but Clang does currently not perform that optimization
}
int g(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10)
{
[[clang::nonportable_musttail]]
return a(1, a2); // legal, x64 sysv abi is caller pop (if there are stack args at all)
}
int h(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11)
{
[[clang::nonportable_musttail]]
return c(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); // legal, same reason as g
}
short i();
int j()
{
[[clang::nonportable_musttail]]
return i(); // expected-error {{upper bits of a int16 return value are garbage on x64 sysv abi, this return must sign extend}}
}
int k();
short l()
{
[[clang::nonportable_musttail]]
return k(); // legal, but Clang does currently not perform that optimization
} The legal-but-unimplemented ones can be marked expected-error for now with a fixme comment, but I would like to see them in the tests. |
Sounds like tests for LLVM backends. |
Not if it's going to be generating diagnostics, right? |
Right, perhaps we should change the behavior of the existing attribute to weaken it, and create a new attribute Looking at |
Adding features that are target-specific in a non-obvious way is very likely to cause issues for anyone targeting an uncommon platform... writing portable C is hard enough without adding new pitfalls. As noted at #54964 (comment), there are solutions that allow portable code without restricting the legal signatures. Do we have some specific use-case in mind? |
I want to reiterate that the existing diagnostics for In other words, I think LLVM needs to change first. LLVM would need to relax its checks around llvm-project/llvm/lib/IR/Verifier.cpp Lines 3589 to 3681 in 436758f
This can't be relaxed in Clang alone. |
I am not sure if we can relax existing musttail, probably we would need a new call marker.. |
I am not sure. This would be one rule, plus consider case by @jacobsa , plus X in the future. I believe people here wants best-effort musttail attribute and they are okay in diagnostics coming from individual backends. |
Yes, to be clear what I want is "give a diagnostic unless you are able to turn this into a tail call on this platform". In other words, I want to be sure the function will not blow the stack, but I don't care (in this case) about portability. Or at least not about portability to all of the exotic platforms that clang supports. |
Based on info from llvm#54964
Coming from the function programming world, let me point out that tail-call elimination is applicable whenever the return type is compatible, and nothing needs to be done with the result other than return it. It shouldn't matter if there are more or fewer parameters or anything else. That said, I agree with one of the comments above that many use cases would be addressed as long as there are no more parameters in the target function than in the calling function - or more accurately, if I understand the comment correctly, if no additional stack space needs to be allocated. But right now (in Zig) I have to cast parameters in semantically-incorrect ways in order to get type signatures to match so that I can get tail-calls to work properly. That is ugly and unfortunate. |
Hi, is there any update on the implementation of [[clang::nonportable_musttail]]? Just wondering if it's being worked on. Thanks! |
…signature (#127366) This commit is for #107569 and #126817. Stop changing musttail's caller and callee's function signature when calling convention is not swifttailcc nor tailcc. Verifier makes sure musttail's caller and callee shares exactly the same signature, see commit 9ff2eb1 and #54964. Otherwise just make sure the return type is the same and then process musttail like usual calls. close #107569, #126817
…signature (llvm#127366) This commit is for llvm#107569 and llvm#126817. Stop changing musttail's caller and callee's function signature when calling convention is not swifttailcc nor tailcc. Verifier makes sure musttail's caller and callee shares exactly the same signature, see commit 9ff2eb1 and llvm#54964. Otherwise just make sure the return type is the same and then process musttail like usual calls. close llvm#107569, llvm#126817
So clearly LLVM can apply tail call optimization for these cases. But Clang does not think so and emits errors for these cases:
error: cannot perform a tail call to function 'base' because its signature is incompatible with the calling function
int f1() { [[clang::musttail]] return base(5); }
^
error: cannot perform a tail call to function 'base' because its signature is incompatible with the calling function
int f2(int x, int y /* unused */) { [[clang::musttail]] return base(5); }
https://godbolt.org/z/7h6h1YjWT
The text was updated successfully, but these errors were encountered: