-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Explicit Tail Calls #3407
base: master
Are you sure you want to change the base?
Explicit Tail Calls #3407
Conversation
thanks a ton @phi-go for all the work you put into writing the RFC so far! Really appreciated! 🎉 |
While I personally know the abbreviation TCO, I think that it would be helpful to expand the acronym in the issue title for folks who might not know it at first glance. |
An alternative is to mark a function (like in OCaml), not a recursive fn x() {
let a = Box::new(());
let b = Box::new(());
y(a);
} |
The RFC already highlights an alternative design with markers on function declarations and states that tail calls are a property of the function call and not a property of a function declaration since there are use cases where the same function is used in a normal call and a tail call. |
Note: this may be suitable either as a comment on #2691 or here. I'm assuming interested parties are watching both anyway. The restriction on caller and callee having the exact same signature sounds quite restrictive in practice. Comparing it with [Pre-RFC] Safe goto with value (which does a similar thing to the If we translate it to the
I don't think this should be handled by an actual calling convention label though. Calling these "rusttail" functions for discussion purposes, we have the following limitations:
The user doesn't have to see either of these limitations directly though; the compiler can just generate a shim with the usual (or specified) calling convention which calls the "rusttail" function if required. Instead, they just observe the following restrictions, which are strictly weaker than the one in the RFC:
|
@digama0 the
I'll try to implement those relaxations in the experiment (once the things described in the RFC right now are fully implemented and merged...), but I also want to highlight @phi-go's mention that those relaxations can be RFC-ed separately from this RFC (and they probably should, to keep the scope smaller!). |
@Robbepop Using |
Un-nominating the RFC for now. To be explicitly clear on next steps: 👍 for going ahead with one or more experiments (including a tail-call with placeholder keyword, and a tail call calling-convention as @scottmcm suggested), blocking concerns for an experiment or RFC that uses the demo-looks-done approach of |
I wouldn't say it is a non-starter. The same reasoning could have been applied to the highly debated
If during all the time of the RFC thread I'd have seen a single alternative syntax that was actually cutting it, I'd wholeheartedly agree. Yet, we do not even has consensus what an agreeable placeholder syntax could look like. A bit of direction from the people who are deciding over nomination could probably help here. In light of the RFC's un-nomination I would like to know what the status of the work behind the RFC is as this RFC thread has been very silent since quite a few weeks now. What in particular is the stance of @WaffleLapkin (RFC implementer) and @phi-go (RFC author) about the next steps provided by @joshtriplett ? I personally just hope that this RFC won't die due to too much bikeshedding. It would be really sad to lose the momentum that has been built up for this long awaited Rust feature. |
To my understanding we are just waiting for the implementation of the experiment. Here is the tracking issue: rust-lang/rust#112788. So un-nominating until the implementation is done seems fine to me. Regarding the placeholder syntax, we are indeed waiting on a decision for the actual syntax. The current state is that the current implementation uses I find |
feature: Add basic support for `become` expr/tail calls This follows rust-lang/rfcs#3407 and my WIP implementation in the compiler. Notice that I haven't even *opened* a compiler PR (although I plan to soon), so this feature doesn't really exist outside of my WIP branches. I've used this to help me test my implementation; opening a PR before I forget. (feel free to ignore this for now, given all of the above)
the rfc does not link to the tracking issue |
@lolbinarycat Looking at other RFCs, the tracking issue does not seem to be included. A link is in the first comment here anyway, so seems fine to me. |
a lot of other RFCs don't have tracking issues. it's supposed to go under the |
I see, updated. I thought those should be filled in when the RFC is accepted. |
I stumbled on this earlier: https://github.com/bytecodealliance/rfcs/blob/main/accepted/pulley.md The wasmtime folks sound interested in using |
Indeed we are! We even have an open PR that is just about ready to merge and adds a switch to turn |
This came up multiple times, with positive affection, in recent extended lang meetings. And since then, I've become aware of an intriguing use case for this that involves interoperability with Rust and our ecosystem. Seemingly it is on our minds. |
According to GH search algorithm, this thread doesn't contain any instance of "successor" or "combinator". I'm somewhat surprised. I've been experimenting with those, both in Rust and TypeScript (the link is mostly TS, but I have repos where I use I assume the combinator approach is more efficient than a trampoline.
IMO, the RFC should also compare the pros and cons of using those alternatives. The first one that comes to mind is that |
Co-authored-by: Ricardo Fernández Serrata <[email protected]>
Co-authored-by: Ricardo Fernández Serrata <[email protected]>
Thanks for taking a look! I have to say I'm not really familiar with successors and combinators, they didn't come up during the discussion as you already noticed. Looking at those they seem very interesting. At least if one is not doing general tail recursion, which would also be a con as to why they cannot replace explicit tail calls, if I understood that right. I will probably have time to add a section on those this weekend. |
@Rudxain I added a section, if you have time I would appreciate if you could take a look. I left out the part regarding const as it is not really a fundamental problem and could change in the future. |
text/0000-explicit-tail-calls.md
Outdated
|
||
### Principled Local Goto | ||
One alternative would be to support some kind of local goto natively, indeed there exists a | ||
[pre-RFC](https://internals.rust-lang.org/t/pre-rfc-safe-goto-with-value/14470/9?u=scottmcm) ([comment](https://github.com/rust-lang/rfcs/issues/2691#issuecomment-1458604986)) or another (LLVM specific idea)[https://internals.rust-lang.org/t/idea-for-safe-computed-goto-using-enums/21787?u=programmerjake]. These designs should be able to achieve the targeted performance and stack usage, though they seems to be quite difficult to implement or are backend specific. Also they do not seem to be as flexible as the chosen design, especially implementing tail calls to indirect or external functions do not seem to be feasible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the computed goto idea I proposed isn't LLVM specific, since computed goto originally was a gcc extension for C/C++ before LLVM started, so it should work just fine in gcc too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo computed goto is more useful than tail calls in some situations (e.g. better register allocation due to not being restricted by the function call ABI) and less useful in others, so I think both tail calls and computed goto are worth adding to rust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle there's nothing preventing an optimizer from doing the same sort register allocation with tail calls, if the callees are equivalently local to the goto targets.
And on the other hand, in practice tail calls using a fixed ABI can produce better register allocation than computed goto, because a big irreducible CFG can trip up the allocator's heuristics in ways that separate functions can be written to avoid.
The better argument for computed goto is to unlock intraprocedural borrow checking, but even then it should probably look more like a tail call to a local function (with parameters), analogous to break-with-value, and less like the C extension, which doesn't play well with block scope or destructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the following Lisp-inspired syntax?
tagbody! {
'a: { /* do some work */ goto 'b; }
'b: { /* do some work */ 1 }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the following Lisp-inspired syntax?
tagbody! { 'a: { /* do some work */ goto 'b; } 'b: { /* do some work */ 1 } }
the syntax I had proposed is essentially equivalent to that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@programmerjake Would you agree that local gotos require backend support? Then I would update that section to mention that instead.
I would also add some text that local gotos could have other applications than for tail calls. While this seems to already be disputed I guess we do not really know until this is tried?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@programmerjake Would you agree that local gotos require backend support?
yes, assuming you meant computed goto (jumping to an address in a variable instead of to a known label), but afaik all targets on gcc and/or llvm already support computed goto -- the main issue i raised in that internals thread is LLVM doesn't have a way to refer to a label's address from another codegen unit, so that needs to be fixed or worked around if we're using enums and not just a raw pointer to hold the addresses of our labels.
Then I would update that section to mention that instead.
ok
I would also add some text that local gotos could have other applications than for tail calls.
computed gotos do not let you make tail calls, they only work within a function instead of calling between functions. they can just be used for similar applications such as making fast interpreters.
gotos in general are also useful for expressing irreducible control flow, if we got computed goto, it can also be used as a normal goto to express that control flow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, assuming you meant computed goto (jumping to an address in a variable instead of to a known label), but afaik all targets on gcc and/or llvm already support computed goto -- the main issue i raised in that internals thread is LLVM doesn't have a way to refer to a label's address from another codegen unit, so that needs to be fixed or worked around if we're using enums and not just a raw pointer to hold the addresses of our labels.
Ah, thanks for the clarification. So for a known label this is in theory possible without backend support?
computed gotos do not let you make tail calls, they only work within a function instead of calling between functions. they can just be used for similar applications such as making fast interpreters.
Yeah, sorry I meant other use cases than being a possible alternative to the functionality provided by tail calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks for the clarification. So for a known label this is in theory possible without backend support?
Yes, MIR is a CFG already. Jumping to a known label uses the exact same MIR block terminator as is used for eg control flow converging back to the same code path after an if else. Only the MIR building code and steps before it would need to be updated.
Co-authored-by: Ricardo Fernández Serrata <[email protected]>
Co-authored-by: Ricardo Fernández Serrata <[email protected]>
This RFC proposes a feature to provide a guarantee that function calls are tail-call eliminated via the
become
keyword. If this guarantee can not be provided an error is generated instead.Rendered
For reference, previous RFCs #81 and #1888, as well as an earlier issue #271, and the currently active issue #2691.
Rust tracking issue: rust-lang/rust#112788