Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upguaranteed tail call elimination #81
Conversation
This comment has been minimized.
This comment has been minimized.
|
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? |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
Of course, I'm hugely in favor of this if we can pull it off. It promises to be a PR windfall as well. |
This comment has been minimized.
This comment has been minimized.
|
@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. |
bstrie
reviewed
May 19, 2014
|
|
||
| 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. |
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
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.
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
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.
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
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".
This comment has been minimized.
This comment has been minimized.
|
Re: using |
bstrie
reviewed
May 19, 2014
| * 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. |
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
Can you elaborate on this last point, of our current calling convention getting in the way?
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
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.
bstrie
reviewed
May 19, 2014
| The current requirements proposed above are very strict and could be relaxed: | ||
|
|
||
| * The lint could be informed that a type like `Rc` will have a stable representation and | ||
| allow passing it to the callee. |
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
Do you mean by special-casing it into the compiler, or via an opt-in attribute? Because the former is a little gross, and the latter would be a dismayingly-specific thing for library authors to consider attaching to all of their types.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
Could such an attribute have any uses beyond just allowing your type to play nicely with TCE?
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
If Rust is ever going to support versioned ABIs where bugs can be fixed without breaking the ABI and recompiling every program linked against the library, something like that would be necessary.
bstrie
reviewed
May 19, 2014
| This feature only just landed in LLVM, and Rust will need to leave this feature gated as the kinks | ||
| are worked out. The support is not completely solid on every platform yet, but it's designed to be a | ||
| portable feature and will produce an error if the requirements are not met or the platform support | ||
| is incomplete. |
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
What platforms specifically have solid support for this at the moment?
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
Proper support for x86 was added on April 29th, other platforms haven't had the work done yet. It's a very new feature. That's why I'm proposing the introduction of this feature behind a feature gate.
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
I'm somewhat afraid of us drumming up hype for this feature only to be left holding the bag if, six months from now, only x86 and x64 are supported with no ARM patches on the horizon. Having no idea what the development process for LLVM is like, is there any sort of concrete commitment to supporting this feature on less-popular architectures? In the worst-case scenario, would the semantics of become be such that we could gracefully degrade it into a return while "only" losing the promise that it won't blow the stack?
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
I'm somewhat afraid of us drumming up hype for this feature only to be left holding the bag if, six months from now, only x86 and x64 are supported with no ARM patches on the horizon
It doesn't matter how long it takes for the feature to be finished in LLVM because it will be behind a feature gate until it's solid enough for Rust. If someone is upset about how long it's taking to finish, they can write the ARM code generation themselves.
Having no idea what the development process for LLVM is like, is there any sort of concrete commitment to supporting this feature on less-popular architectures?
The patch adding x86 / x86_64 support changed 95 lines. It's not like it would a substantial task to implement this for other architectures. It's part of the portable language specification so any target not implementing it can be considered incomplete and not supported upstream. You can consider the fact that it's a defined part of the LLVM language specification to be a strong commitment to implementing it.
In the worst-case scenario, would the semantics of
becomebe such that we could gracefully degrade it into areturnwhile "only" losing the promise that it won't blow the stack?
Degrading to a return would be an LLVM code generation bug. It will currently report a not implemented error for cases not yet correctly handled per the language specification. This isn't something Rust has to worry about, it simply remains feature gated until enough support is implemented.
bstrie
reviewed
May 19, 2014
| The requirement of not accessing the caller's allocas is the hardest to enforce. The compiler would | ||
| forbid passing non-immediate types, and a default-deny lint would forbid passing non-primitive types | ||
| since the representation may change. The compiler would also forbid passing any lifetime-bounded | ||
| types not guaranteed to outlive the caller. |
This comment has been minimized.
This comment has been minimized.
bstrie
May 19, 2014
Contributor
I want to understand these better. Can you whip up a compile-fail example for each of the three restrictions in this paragraph, along with a short description of why the programs must be rejected? I feel like it's going to be very important to communicate our restrictions up-front, and this will help to that end.
This comment has been minimized.
This comment has been minimized.
|
I love become for reasons that don't really matter. |
matthieu-m
reviewed
May 19, 2014
| * 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. |
This comment has been minimized.
This comment has been minimized.
matthieu-m
May 19, 2014
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.
This comment has been minimized.
This comment has been minimized.
thestinger
May 19, 2014
Author
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.
This comment has been minimized.
This comment has been minimized.
bstrie
May 21, 2014
Contributor
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.
This comment has been minimized.
This comment has been minimized.
dherman
May 21, 2014
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.
This comment has been minimized.
This comment has been minimized.
dherman
commented
May 21, 2014
|
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. |
This comment has been minimized.
This comment has been minimized.
matthieu-m
commented
May 22, 2014
|
I do not understand why In a way, I see Of course, I would suggest starting without it, it is backward compatible anyway. |
This comment has been minimized.
This comment has been minimized.
|
It would be quite confusing if |
This comment has been minimized.
This comment has been minimized.
|
I must be missing something, how would it be possible to have both |
This comment has been minimized.
This comment has been minimized.
|
@bstrie: That's right, I didn't notice from the example. |
This comment has been minimized.
This comment has been minimized.
dherman
commented
May 24, 2014
|
@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). |
This comment has been minimized.
This comment has been minimized.
dherman
commented
May 24, 2014
|
@matthieu-m: The purpose of an explicit tail calling operator ( |
This comment has been minimized.
This comment has been minimized.
|
@dherman: The ABI has to match for |
alexcrichton
added
the
postponed
label
Jun 16, 2014
This comment has been minimized.
This comment has been minimized.
|
Closing, this was discussed a few weeks ago where we decided to postpone this. |
alexcrichton
closed this
Jun 16, 2014
rust-highfive
referenced this pull request
Sep 24, 2014
Closed
guaranteed tail call elimination #271
This comment has been minimized.
This comment has been minimized.
graue
commented
Oct 5, 2014
|
Updated discussion link for any other curious people reading this :) |
thestinger
deleted the
thestinger:become
branch
Nov 12, 2014
This comment has been minimized.
This comment has been minimized.
mrhota
commented
Oct 10, 2016
|
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 |
This comment has been minimized.
This comment has been minimized.
|
The next iteration - #1888 (also postponed). |
thestinger commentedMay 19, 2014
No description provided.