-
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
Proper tail calls #1888
Closed
Closed
Proper tail calls #1888
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f1d3282
RFC for proper tail calls
DemiMarie 7fbf2c1
Provide link to implementation
DemiMarie 55e4056
Add feature name
DemiMarie 7f9fb23
typo: varient is spelled variant
DemiMarie c7b83dc
Create a new file with RFC instead of changing template
DemiMarie 788fd7e
Add license
DemiMarie 1e7d448
Fix 404 link in RFC
jmcomets 79d936f
Merge pull request #1 from jmcomets/patch-1
DemiMarie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,167 @@ | ||
- Feature Name: (fill me in with a unique ident, my_awesome_feature) | ||
- Feature Name: proper_tail_calls | ||
- Start Date: (fill me in with today's date, YYYY-MM-DD) | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
One para explanation of the feature. | ||
This RFC adds explicit proper tail calls to Rust via the `become` keyword. | ||
It also specifies the semantics of proper tail calls and their interaction | ||
with destructors. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Why are we doing this? What use cases does it support? What is the expected outcome? | ||
Proper tail calls are commonly found in functional languages, such as OCaml, | ||
SML, Haskell, Scheme, and F#. They also can be used to easily encode state | ||
machines and jump tables. Furthermore, they allow for code to be written in | ||
continuation-passing style, which may be useful for some functional programming | ||
patterns. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
This is the bulk of the RFC. Explain the design in enough detail for somebody familiar | ||
with the language to understand, and for somebody familiar with the compiler to implement. | ||
This should get into specifics and corner-cases, and include examples of how the feature is used. | ||
## Syntax | ||
[syntax]: #syntax | ||
|
||
A proper tail call is written using the `become` keyword. This is already | ||
reserved, so there is no backwards-compatibility break. | ||
|
||
The `become` keyword must be followed by a function or method calls. | ||
Invocations of overloaded operators with at least one non-primitive argument | ||
were considered as valid targets, but were rejected on grounds of being too | ||
error-prone. In any case, these can still be called as methods. | ||
|
||
A future RFC may allow `become` in more places. | ||
|
||
## Type checking | ||
[typechecking]: #typechecking | ||
A `become` statement is type-checked exactly like a `return` statement. In the | ||
current implementation, the syntactic restrictions on `become` noted above are | ||
enforced during this phase. Additionally, the callee **must** use either the | ||
`rust` calling convention or the `rust-call` calling convention. | ||
|
||
## Borrowchecking and Runtime Semantics | ||
[semantics]: #semantics | ||
|
||
A `become` expression acts as if the following events occurred in-order: | ||
|
||
1. All variables that are being passed by-value are moved to temporary storage. | ||
2. All local variables in the caller are destroyed according to usual Rust | ||
semantics. Destructors are called where necessary. Note that values | ||
moved from in step 1 are _not_ dropped. | ||
3. The caller's stack frame is removed from the stack. | ||
4. Control is transferred to the callee's entry point. | ||
|
||
This implies that it is invalid for any references into the caller's stack frame | ||
to outlive the call. | ||
|
||
The borrow checker ensures that none of the above steps will result in the use | ||
of a value that has gone out of scope. | ||
|
||
An implementation is required to support an unlimited number of proper tail | ||
calls without exhausting any resources. | ||
|
||
## Implementation | ||
[implementation]: #implementation | ||
|
||
A current, mostly-functioning implementation can be found at | ||
[DemiMarie/rust/tree/explicit-tailcalls](/DemiMarie/rust/tree/explicit-tailcalls). | ||
|
||
The parser parses `become` exactly how it parses the `return` keyword. The | ||
difference in semantics is handled later. | ||
|
||
During type checking, the following are checked: | ||
|
||
1. The target of the tail call is, in fact, a call. | ||
2. The target of the tail call has the proper ABI. | ||
|
||
Later phases in the compiler assert that these requirements are met. | ||
|
||
New nodes are added in HIR and HAIR to correspond to `become`. In MIR, however, | ||
a new flag is added to the `TerminatorKind::Call` variant. This flag is only | ||
allowed to be set if all of the following are true: | ||
|
||
1. The destination is `RETURN_POINTER`. | ||
2. There are no cleanups. | ||
3. The basic block being branched into has length zero. | ||
4. The basic block being branched into terminates with a return. | ||
|
||
Trans will assert that the above are in fact true. | ||
|
||
Finally, the use of proper tail calls must be propogated to LLVM. This is done | ||
in two ways: | ||
|
||
1. Turn on tail call optimization. This is done by setting | ||
`Options.GuaranteedTailCallOpt` in | ||
[PassWrapper.cpp](src/rustllvm/PassWrapper.cpp). | ||
2. Make the actual call a tail call. This is done by means of the following | ||
function, added to [RustWrapper.cpp](src/rustllvm/RustWrapper.cpp): | ||
|
||
```c++ | ||
extern "C" void LLVMRustSetTailCall(LLVMValueRef Instr) { | ||
CallInst *Call = cast<CallInst>(unwrap<Instruction>(Instr)); | ||
Call->setTailCall(); | ||
} | ||
``` | ||
|
||
# How We Teach This | ||
[how-we-teach-this]: #how-we-teach-this | ||
|
||
What names and terminology work best for these concepts and why? | ||
How is this idea best presented—as a continuation of existing Rust patterns, or as a wholly new one? | ||
Tail calls are essentially disciplined cross-function `goto` – a unidirectional | ||
transfer of control. They are a wholly new pattern for Rust, and make others | ||
possible, such as continuation-passing style. | ||
|
||
Would the acceptance of this proposal change how Rust is taught to new users at any level? | ||
How should this feature be introduced and taught to existing Rust users? | ||
Nevertheless, tail calls are an advanced feature that can produce code which is | ||
difficult to debug. As such, they should only be used when other techniques are | ||
not suitable. | ||
|
||
What additions or changes to the Rust Reference, _The Rust Programming Language_, and/or _Rust by Example_ does it entail? | ||
I believe that the first three paragraphs under #detailed-design could be added | ||
to the Rust Reference with only minor changes. New material containing some | ||
use cases would need to be added to _The Rust Programming Language_ and | ||
_Rust by Example_. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Why should we *not* do this? | ||
## Runtime overhead | ||
[runtime overhead]: #runtime-overhead | ||
|
||
One major drawback of proper tail calls is that their current implementation in | ||
LLVM is not zero-cost: it forces a callee-pops calling convention, and thus | ||
causes a stack adjustment after each non-tail call. This could be a performance | ||
penalty. | ||
|
||
## Portability | ||
[portability]: #portability | ||
|
||
An even greater drawback of proper tail calls is lack of cross-platform support: | ||
LLVM does not support proper tail calls when targeting MIPS or WebAssembly, and | ||
a compiler that generated C code would be hard-pressed to support them. While | ||
relying on sibling call optimization in the C compiler might be possible with | ||
whole-program compilation, it would still be tricky. WebAssembly does not | ||
support tail calls at all yet, so stablization of this feature will need to wait | ||
until this changes, which could take years. | ||
|
||
In fact, this is such a drawback that I (Demi Marie Obenour) considered not | ||
making this RFC at all. Rust language features (as opposed to library features) | ||
should work everywhere. That this does not is unfortunate. | ||
|
||
## Debugability | ||
[debugability]: #debugability | ||
|
||
Proper tail calls can make debugging difficult, by overwriting stack frames. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
What other designs have been considered? What is the impact of not doing this? | ||
Proper tail calls are not necessary. Rust has done fine without them, and will | ||
continue to do so if this RFC is not accepted. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
What parts of the design are still TBD? | ||
Is there a way to emulate proper tail calls when compiling to targets that don't | ||
support them? Is it possible to eliminate the overhead imposed on every | ||
non-tail call? |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 404s for me.