Skip to content

explicit tail calls: support indirect arguments#151143

Merged
rust-bors[bot] merged 2 commits intorust-lang:mainfrom
folkertdev:tail-call-indirect
Feb 28, 2026
Merged

explicit tail calls: support indirect arguments#151143
rust-bors[bot] merged 2 commits intorust-lang:mainfrom
folkertdev:tail-call-indirect

Conversation

@folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Jan 14, 2026

View all comments

tracking issue: #112788

After discussion in #144855, I was wrong and it is actually possible to support tail calls with PassMode::Indirect { on_stack: false, .. } arguments.

Normally an indirect argument with on_stack: false would be passed as a pointer into the caller's stack frame. For tail calls, that would be unsound, because the caller's stack frame is overwritten by the callee's stack frame.

Therefore we store the argument for the callee in the corresponding caller's slot. Because guaranteed tail calls demand that the caller's signature matches the callee's, the corresponding slot has the correct type.

To handle cases like the one below, the tail call arguments must first be copied to a temporary, and can only then be copied to the caller's argument slots.

// A struct big enough that it is not passed via registers.
pub struct Big([u64; 4]);

fn swapper(a: Big, b: Big) -> (Big, Big) {
    become swapper_helper(b, a);
}

I'm not really familiar with MIR and what tricks/helpers we have, so I'm just cobbling this together. Hopefully we can arrive at something more elegant.

@folkertdev folkertdev added the F-explicit_tail_calls `#![feature(explicit_tail_calls)]` label Jan 14, 2026
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 14, 2026
@folkertdev folkertdev force-pushed the tail-call-indirect branch 2 times, most recently from 8a1cd78 to 6f7eede Compare January 14, 2026 21:41
Comment on lines +1270 to +1279
PassMode::Indirect { on_stack: true, .. } => {
// FIXME: some LLVM backends (notably x86) do not correctly pass byval
// arguments to tail calls (as of LLVM 21). See also:
//
// - https://github.com/rust-lang/rust/pull/144232#discussion_r2218543841
// - https://github.com/rust-lang/rust/issues/144855
span_bug!(
fn_span,
"arguments using PassMode::Indirect {{ on_stack: true, .. }} are currently not supported for tail calls"
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've kept the span_bug! for now.

It looks like a fix for x86 might get cherry-picked into LLVM 22. If so, I think that is enough support to allow this variant too. At that point riscv would be the next most commonly used target that would miscompile.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@folkertdev folkertdev force-pushed the tail-call-indirect branch 2 times, most recently from 029786e to 414b969 Compare January 15, 2026 17:28
@folkertdev
Copy link
Contributor Author

@bors try=x86_64-msvc-1,x86_64-msvc-2

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 15, 2026

Unknown command "try". Run @bors help to see available commands.

@folkertdev
Copy link
Contributor Author

@bors try job=x86_64-msvc-1,x86_64-msvc-2

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Jan 15, 2026
explicit tail calls: support indirect arguments


try-job: x86_64-msvc-1
try-job: x86_64-msvc-2
@rust-log-analyzer

This comment has been minimized.

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 15, 2026

💔 Test for e8cb9bb failed: CI. Failed job:

@folkertdev
Copy link
Contributor Author

@bors try job=x86_64-msvc-1,x86_64-msvc-2

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Jan 15, 2026
explicit tail calls: support indirect arguments


try-job: x86_64-msvc-1
try-job: x86_64-msvc-2
@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 16, 2026

☀️ Try build successful (CI)
Build commit: 282e2c8 (282e2c837faf975d875d5007dcbbdb5567a62f41, parent: b5c2a0fc0ac851e83ef943e3a2b90c1abab06baa)

@folkertdev
Copy link
Contributor Author

This seems to work, though we should probably run a bunch of try jobs once the code looks good.

r? @WaffleLapkin

@rustbot
Copy link
Collaborator

rustbot commented Jan 16, 2026

WaffleLapkin is currently at their maximum review capacity.
They may take a while to respond.

@folkertdev folkertdev marked this pull request as ready for review January 16, 2026 17:09
@folkertdev
Copy link
Contributor Author

We actually run these tests using webassembly (a tier 2 target)?

@bors try job=test-various

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Feb 27, 2026
explicit tail calls: support indirect arguments


try-job: test-various
@folkertdev
Copy link
Contributor Author

@bors try cancel

github is doing something weird, my latest push is not showing up...

@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 27, 2026

Try build cancelled. Cancelled workflows:

@rustbot
Copy link
Collaborator

rustbot commented Feb 27, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@folkertdev
Copy link
Contributor Author

@bors try job=test-various

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Feb 27, 2026
explicit tail calls: support indirect arguments


try-job: test-various
@rust-log-analyzer

This comment has been minimized.

@folkertdev
Copy link
Contributor Author

@bors try job=test-various

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Feb 27, 2026
explicit tail calls: support indirect arguments


try-job: test-various
@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 27, 2026

☀️ Try build successful (CI)
Build commit: b8a6c0d (b8a6c0d1a7f881896a4e68b3a59e5a419c4bade6, parent: 0ee5907d597b02afadb5daa26a60fedb72f098d1)

@folkertdev
Copy link
Contributor Author

@bors r=WaffleLapkin

@rust-bors
Copy link
Contributor

rust-bors bot commented Feb 27, 2026

📌 Commit e6cf5a2 has been approved by WaffleLapkin

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 27, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Feb 27, 2026
…affleLapkin

explicit tail calls: support indirect arguments

tracking issue: rust-lang#112788

After discussion in rust-lang#144855, I was wrong and it is actually possible to support tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.

Normally an indirect argument with `on_stack: false` would be passed as a pointer into the caller's stack frame. For tail calls, that would be unsound, because the caller's stack frame is overwritten by the callee's stack frame.

Therefore we store the argument for the callee in the corresponding caller's slot. Because guaranteed tail calls demand that the caller's signature matches the callee's, the corresponding slot has the correct type.

To handle cases like the one below, the tail call arguments must first be copied to a temporary, and can only then be copied to the caller's argument slots.

```rust
// A struct big enough that it is not passed via registers.
pub struct Big([u64; 4]);

fn swapper(a: Big, b: Big) -> (Big, Big) {
    become swapper_helper(b, a);
}
```

---

I'm not really familiar with MIR and what tricks/helpers we have, so I'm just cobbling this together. Hopefully we can arrive at something more elegant.
rust-bors bot pushed a commit that referenced this pull request Feb 27, 2026
…uwer

Rollup of 12 pull requests

Successful merges:

 - #151143 (explicit tail calls: support indirect arguments)
 - #153012 (Stop using `LinkedGraph` in `lexical_region_resolve`)
 - #153175 (Clarify a confusing green-path function)
 - #153179 (Force a CI LLVM stamp bump)
 - #150828 (Improved security section in rustdoc for `current_exe`)
 - #152673 (rustc_public: rewrite `bridge_impl` to reduce boilerplate)
 - #152674 (rustc_public: remove the `CrateDefItems` trait)
 - #153073 (Fix mem::conjure_zst panic message to use any::type_name instead)
 - #153117 (Remove mutation from macro path URL construction)
 - #153128 (Recover feature lang_items for emscripten)
 - #153138 (Print path root when printing path)
 - #153159 (Work around a false `err.emit();` type error in rust-analyzer)
rust-bors bot pushed a commit that referenced this pull request Feb 27, 2026
…uwer

Rollup of 12 pull requests

Successful merges:

 - #151143 (explicit tail calls: support indirect arguments)
 - #153012 (Stop using `LinkedGraph` in `lexical_region_resolve`)
 - #153175 (Clarify a confusing green-path function)
 - #153179 (Force a CI LLVM stamp bump)
 - #150828 (Improved security section in rustdoc for `current_exe`)
 - #152673 (rustc_public: rewrite `bridge_impl` to reduce boilerplate)
 - #152674 (rustc_public: remove the `CrateDefItems` trait)
 - #153073 (Fix mem::conjure_zst panic message to use any::type_name instead)
 - #153117 (Remove mutation from macro path URL construction)
 - #153128 (Recover feature lang_items for emscripten)
 - #153138 (Print path root when printing path)
 - #153159 (Work around a false `err.emit();` type error in rust-analyzer)
@rust-bors rust-bors bot merged commit 1b897d4 into rust-lang:main Feb 28, 2026
12 checks passed
@rustbot rustbot added this to the 1.95.0 milestone Feb 28, 2026
rust-timer added a commit that referenced this pull request Feb 28, 2026
Rollup merge of #151143 - folkertdev:tail-call-indirect, r=WaffleLapkin

explicit tail calls: support indirect arguments

tracking issue: #112788

After discussion in #144855, I was wrong and it is actually possible to support tail calls with `PassMode::Indirect { on_stack: false, .. }` arguments.

Normally an indirect argument with `on_stack: false` would be passed as a pointer into the caller's stack frame. For tail calls, that would be unsound, because the caller's stack frame is overwritten by the callee's stack frame.

Therefore we store the argument for the callee in the corresponding caller's slot. Because guaranteed tail calls demand that the caller's signature matches the callee's, the corresponding slot has the correct type.

To handle cases like the one below, the tail call arguments must first be copied to a temporary, and can only then be copied to the caller's argument slots.

```rust
// A struct big enough that it is not passed via registers.
pub struct Big([u64; 4]);

fn swapper(a: Big, b: Big) -> (Big, Big) {
    become swapper_helper(b, a);
}
```

---

I'm not really familiar with MIR and what tricks/helpers we have, so I'm just cobbling this together. Hopefully we can arrive at something more elegant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

F-explicit_tail_calls `#![feature(explicit_tail_calls)]` S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants