To be clear, this is a debuging aid only? It's not something required
for correctness? I'm somewhat bothered by that because it seems like it
would be a useful implementation tool for higher level languages.
It's not purely a debugging aid that helps when you are using the
debugger. There are projects (that are not debuggers) that rely on not
missing frames to produce results that are useful.
If it's not simply best effort, that constrains our choices.
A couple of thoughts in no particular order:
1) Can we always annotate the call site rather than the function? That
removes the unpredictability due to optimization.
Annotating the call site should be fine. For the use cases that we care
about, it probably isn't important to prevent tail calls on indirect calls.
Given this, I would lean towards a notail value being added as an
alternative to "tail" and "musttail". This seems to fit the existing uses,
doesn't have any obvious loop holes or best effort semantics, and solves
the problem at hand.
2) Calling it something like "no-direct-tail-call" or "prefer-no-tail"
would remove some of the confusion value. When I see "notail", I expect
that to always be respected; the best effort semantics come as a bit of a
surprise.
I agree. A name that indicates it's only a best effort option or it's an
option that affects only direct calls would be good.
(This only applies if we're talking about a function annotation. The call
site annotation applies to both direct and indirect calls.)
3) This seems analogous to the "tail" marker in that it indicates a
preference/option. Whatever we end up with, it needs to be a verifier
option to have a "tail" or "musttail" call site which is also "notail". It
also needs to be an error to have a mustail callsite to a notail function
(if such ends up existing.)
If we are going to annotate the function, I think we should have the
verifier catch incompatibilities between the markers on the call sites and
the function attribute on the called functions.
If we are annotating the call site, the verifier check isn't needed since
the tail-call related markers are enums that are mutually exclusive.
Yep.
4) It somewhat feels like there are two concepts being intermixed here.
1) A call site which will never be a tail call. 2) A function which we
prefer not to tail call to. Does it make sense to separate them?
Yes, it makes sense to separate them. For the use case we care about,
either 1) or 2) will do. We don't have to have support for both.
I would lean toward doing (1) for now. We can come back and implement (2)
at a later time if we find it's needed. After (1), each call site will
have four states:
- "notail" - Can not be a tail call.
- "" - May be a tail call if analysis finds it legal, profitable, and
desirable*
- "tail" - May be a tail call, profitability hinted
- "musttail" - Must be a tail call.
* (2) would basically just change the desirability of moving from "" to
"tail".
OK. I'm considering changing the direction of this patch and marking the
call site instead of the called function.
We should also discuss what kinds of source level attributes we'll need. My
plan is to attach an attribute that indicates notail (something like
no_direct_tail) to the called function declaration and definition and then
mark all the direct call sites in the IR that call the function as notaill.
In addition to that, it seems like we want to have a way to attach the
attribute directly to the call site:
void (*indirectCall)(int, int, int);
void foo1(int a, int b) {
(*indirectCall)(a, b, c) __attribute__((notail));
}