-
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
guaranteed tail call elimination #81
Conversation
When I asked @thestinger how this proposal deals with the widly-publicized email from a year ago explaining why Rust can't support TCE (https://mail.mozilla.org/pipermail/rust-dev/2013-April/003557.html), his response was along the lines of "LLVM supports this now, so everything is okay." But I'd still like to see the following point from Graydon's email elaborated upon in more detail:
What does he mean by "play badly", and what implications does this have if this feature is accepted? |
I am in favor of this, however should probably clarify what happens to locals. The way I envision it, locals will be destroyed before the tailcall. |
Of course, I'm hugely in favor of this if we can pull it off. It promises to be a PR windfall as well. |
@bstrie: The semantics are equivalent to return immediately after the call, so there's no way to destroy locals after the callee runs. Instead, locals could be destroyed before the call unless they are passed into it by-value but that's a bit weird. |
|
||
This proposal adds an extra keyword to the language, although `be` has been reserved with this in | ||
mind for some time. LLVM now provides this feature, but another backend may need to do extra manual | ||
work to provide this as a guarantee. |
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.
Not that I'm against accepting this feature merely out of concern for some hypothetical alternative implementation, but how much work are we talking here? Using GCC as an example, say.
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.
The commit adding x86 support for musttail was quite small. I don't know how much work this would involve for GCC. The feature is designed to be portable, but involves work for each new architecture.
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.
Thanks. Basically I just want to make sure that it's not so much work that our choices effectively boil down to "never implement Rust on any other backend" vs. "force all alternative implementations to completely ignore a language feature".
Re: using |
* Locals with destructors could be permitted, and would be destroyed *before* the call when not | ||
passing them to the callee. | ||
* Passing non-immediate types as parameters is likely possible, but the current Rust calling | ||
convention will get in the way. |
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.
Can you elaborate on this last point, of our current calling convention getting in the way?
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.
The current convention is to perform a copy in the caller and pass a pointer to the alloca, which is clearly not allowed. AFAICT only passing large values by-value would work.
I love become for reasons that don't really matter. |
* The lint could be informed that a type like `Rc` will have a stable representation and | ||
allow passing it to the callee. | ||
* Locals with destructors could be permitted, and would be destroyed *before* the call when not | ||
passing them to the callee. |
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.
This seems evil: imagine converting a return
into a become
and suddenly you get issues because the behavior changed ?
Instead, I would propose to make it visible: if you want to have locals that have a destructor: no problem, but wrap them in an explicit {}
block so that it's visible they get destroyed before the become
instruction.
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.
Well that's why it's in unresolved questions rather than part of the proposal. The restriction is painful, and it's unclear if there's a sane way of relaxing it.
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.
I recommend that we retain all of the restrictions for the purpose of this RFC. Seeing TCE get used in practice will give us data on which relaxations to pursue, and there's no rush since they're all totally backwards-compatible.
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.
I don't think it's evil; because become
is a control effect, as return
is, it's explicit. But it's a different control effect and does things in different orders.
return
: evaluate argument, then returnbecome
: return, then evaluate argument
Edit: note that the alternative is even more of a refactoring hazard: if you simply can't do a become
in the presence of destructors then non local changes break code.
One thing to keep in mind: in languages with implicit proper tail calls, you can do convenient things like branch on two alternative calls in tail position:
Doing that kind of thing requires you to proliferate
The thing is, I don't see a way to allow
And frankly this defeats the purpose of having an explicit tail calling form in the first place; tail calls inside the body become implicit tail calls. So ISTM the only reasonable syntax is to require the argument of Since we'd expect tail calls to be less common (and loops more common) in Rust than in languages like Scheme that rely on tail calls as the only form of iteration, it might be fine to have a bit of extra Probably that was the intention of the proposal (maybe it said so and I missed it?) but anyway I'm spelling out some consequences. |
I do not understand why In a way, I see Of course, I would suggest starting without it, it is backward compatible anyway. |
It would be quite confusing if |
I must be missing something, how would it be possible to have both |
@bstrie: That's right, I didn't notice from the example. |
@bstrie: "Isn't the function signature of the new function required to match that of the current function?" That's a shame. You can't make it work for arbitrary signatures (obviously with matching return type), with an appropriate amount of stack frame shuffling? Is this based on an LLVM limitation, a lack of desire to implement the shuffling, or a belief that the shuffling is "un-Rusty"? I understand if the latter but it is an unproven middle ground of expressiveness. It also needs to be spelled out just what constitutes matching signatures (eg a monomorphic function tail calling a compatible polymorphic function). |
@matthieu-m: The purpose of an explicit tail calling operator ( |
@dherman: The ABI has to match for |
Closing, this was discussed a few weeks ago where we decided to postpone this. |
Updated discussion link for any other curious people reading this :) |
updated link for the meeting minutes where the postponement was discussed: https://github.com/rust-lang/meeting-minutes/blob/master/weekly-meetings/2014-05-20.md |
The next iteration - #1888 (also postponed). |
No description provided.