-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Pulley: tail calls #9251
Pulley: tail calls #9251
Conversation
86c8be0
to
12d17b7
Compare
Subscribe to Label Actioncc @fitzgen
This issue or pull request has been labeled: "pulley"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
Thanks for this! I might jump in with a small recommendation on overall structure / CI here -- with a Cargo feature controlling this it unfortunately doesn't play well with our "test with all features enabled" in CI well because it enables the feature when a stable compiler is in use. To fix this I might recommend |
Thanks! I'll take a look either later today or tomorrow. Mildly surprised about |
FWIW, it looks like rust-lang/rust#127520 was filed about this regression, but closed as WONTFIX, so I also cross-posted the bug to the |
@fitzgen I'm not sure how it could have worked for you -- either way the reason why rust-lang/rust#127520 was WONTFIXED is that tails calls are simply not implemented yet, they are an incomplete feature. The error even says as much:
there is no reason to bring any more awareness to this issue -- it will be fixed naturally once the implementation is complete. |
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.
Looks great! Really happy with how this shaped up.
Feel free to split out subcommits if you want, but it isn't necessary. Not sure they will actually land first at this point, since the final commit doesn't have as much feedback. Probably good to do for future PRs tho, just to keep things nice and easy for reviewers 👍
Big thing needed to land this is integrating with our CI infrastructure and replacing the cargo feature, as Alex mentioned.
Thanks!
pulley/src/interp.rs
Outdated
fn pc_rel_jump(&mut self, offset: PcRelOffset, inst_size: isize) -> Continuation { | ||
fn pc_rel_jump(&mut self, offset: PcRelOffset, inst_size: isize) { | ||
let offset = isize::try_from(i32::from(offset)).unwrap(); | ||
self.pc = unsafe { self.pc.offset(offset - inst_size) }; | ||
Continuation::Continue |
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.
For caller ergonomics, this could still return a ControlFlow::Continue(())
. This way, we wouldn't need to update every call site to also return that value.
pulley/src/interp.rs
Outdated
enum Continuation { | ||
Continue, | ||
/// The reason the interpreter loop terminated. | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
enum Done { | ||
/// A `ret` instruction was executed and the call stack was empty. This is | ||
/// how the loop normally ends. | ||
ReturnToHost, | ||
|
||
/// a `trap` instruction was executed. | ||
Trap, |
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.
FWIW, the size of ControlFlow<Done>
is going to be bigger than the size of Continuation
was, because there will be two enum
discriminant tags instead of one. But that is fine, and we can measure performance down the line and see if this actually matters. I suspect it might matter if it crosses an ABI threshold where returning these values goes from being in a register to going through a return pointer to a caller-supplied, stack-allocated space. But, as I said, we can always measure 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.
ControlFlow<Done, ()>
and Continuation
are both 1 byte (it seems rustc is smart enough to use the "unused discriminants" from Done
as niches for ControlFlow
).
ControlFlow<Done, OpcodeHandler>
is 2 words. The alternative would be to return ControlFlow<Done, ()>
and set the next handler via an &mut OpcodeHandler
parameter, but I expect that might be harder for LLVM to optimize. In the worst case, returning a large aggregate will be lowered to returning via an out-parameter anyway, so it shouldn't be any worse performance wise
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.
We could also squeeze ControlFlow<Done, OpcodeHandler>
into a single word by using the lower 2 bits as the discriminant tag, since function pointers must be word aligned. I think there was a proposal for rustc to do this automatically but it has not yet been implemented.
2228667
to
5c8e2f8
Compare
51f2406
to
3d19204
Compare
Once #9274 lands, is this ready to merge as well? |
Copyright (c) 2024, Arm Limited. Signed-off-by: Karl Meakin <karl.meakin@arm.com>
Copyright (c) 2024, Arm Limited. Signed-off-by: Karl Meakin <karl.meakin@arm.com>
Copyright (c) 2024, Arm Limited. Signed-off-by: Karl Meakin <karl.meakin@arm.com>
Copyright (c) 2024, Arm Limited. Signed-off-by: Karl Meakin <karl.meakin@arm.com>
3d19204
to
e64c1a4
Compare
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!
After bytecodealliance#9251, we will check that Pulley builds with rustc's experimental tail calls feature enabled in the nightly CI job. So if we touch any pulley source files in a PR, we should also run that job in the PR's CI.
I pushed a commit moving the new CI check to the nightly tests CI job, since it requires a nightly rustc. But it looks like there are some failures running pulley tests under miri now as well, taking a quick look. |
Not the initial PC passed into `run`
Pushed a fix for the miri tests: |
Thanks again @Kmeakin! |
* Run the nightly CI job when `pulley` files are touched in a PR After #9251, we will check that Pulley builds with rustc's experimental tail calls feature enabled in the nightly CI job. So if we touch any pulley source files in a PR, we should also run that job in the PR's CI. * Also run MIRI tests if `pulley` files are changed * review feedback * grep for miri in changed files, not commit messages
/// when compiling without `#![feature(explicit_tail_calls)]` enabled (via | ||
/// `--cfg pulley_tail_calls`). | ||
/// | ||
/// It seems rustc first parses the the function, encounters `become` and emits |
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 the" mistake (typo?)
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.
@Rudxain good eye! Care to make a PR fixing 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.
Nevermind, I see that you already did -- thanks!
Sorry for the delay, I was on holiday.
The proof of concept works, but codegen for
become
is not actually implemented yet and so triggers an ICE:So for now
return
is used instead ofbecome
and we must hope that LLVM will be smart enough to recongize the tail call