Skip to content

RFC: extern "Rust_manyregs" ABI - #4018

Open
veluca93 wants to merge 1 commit into
rust-lang:mainfrom
veluca93:manyregs
Open

veluca93 wants to merge 1 commit into
rust-lang:mainfrom
veluca93:manyregs

Conversation

@veluca93

@veluca93 veluca93 commented Oct 18, 2026 •

Copy link
Copy Markdown

Add a new ABI that tries to pass as many arguments / return values in registers as possible.

Rendered

Comment thread text/0000-manyregs-abi.md
Comment on lines +50 to +53
If the ABI actually uses "as many registers as possible", some ISA extensions
(i.e. avx512 on x86) can extend the number of registers available. Thus,
whether avx512 is enabled on the function can change the ABI, which can lead to
UB.

@tgross35 tgross35 Oct 18, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a huge thing. rustc currently allows building one crate with -Ctarget-feature=+avx512f and link that to a crate without it. So how do we avoid breaking extern "Rust_manyregs" function pointers?

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You don't even need multiple crates, #[target_feature] is enough.

I comment on this below in the RFC, but I have three possible solutions in mind (in order of personal preference):

  • only use 32 vector registers if there is an argument of type _mm512 or similar (if such an argument is present, and #[target_feature] does not contain avx512, the function will fail to compile -- and similarly callsites are checked)
  • stick to 16 GPR and 16 vector registers, always (IIRC effectively equivalent to LLVM's x86_regcall)
  • have extern Rust_manyregs-avx512

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO it seems better to have the ABI depend on which ABI-changing target features you have in the extern "Rust_manyregs-feature1-feature2" ABI string (since that's part of the type of all functions/fnptrs) rather than what target features you enable with compile flags or #[target_feature]. e.g. extern "Rust_manyregs-apx". Obviously you then require those ABI-changing target features to be enabled in both the function and any callers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO it would be much better to start with the low hanging fruit, which is what can be done without language work. If we can use the registers available in the target's baseline then that already gets us quite a lot:

  • i586: 0 gpr -> 8 gpr
  • i686: 0 gpr, 0 vreg -> 8 gpr 8 vreg
  • x86-64: 6 gpr, 8 vreg -> 16 GPR, 16 vreg
  • aarch64: 8 GPR, 8 vreg -> 31 GPR, 32 vreg

(All of those probably have a few reserved GPRs)

That can be done to the default Rust ABI so everybody benefits without opting in, and doesn't even need an RFC or a new LLVM ABI (assuming LLVM's fastcc can be adapted). What's currently written up in this RFC needs a lot more design work on both the Rust and LLVM sides.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't think we can do pass-by-value of ymm/zmm registers on x86 without breaking backwards compatibility (see https://github.com/rust-lang/rust/blob/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_target/src/callconv/mod.rs#L799) - we could do that with a new opt-in ABI.

I don't believe fastcc exposes significantly more registers than the standard ccs - you get a couple more, but AFAIU (from a quick consultation of $LLM) the other biggest difference is that the callee cleans up the stack.

I'm also happy to just expose more ABIs currently existing in LLVM as a simpler initial approach, if we think that would be better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we can do pass-by-value of ymm/zmm registers on x86 without breaking backwards compatibility (see https://github.com/rust-lang/rust/blob/b6eeb93c992027a03e1fd120b6652546396b142c/compiler/rustc_target/src/callconv/mod.rs#L799) - we could do that with a new opt-in ABI.

So, yes, but I'm not looking at only the described change. There are a few separate aspects of the Zulip discussion and what's in this RFC (from a few different people):

  1. Pass types that fit in baseline registers in registers.
  2. Return more values in registers.
  3. Pass more arguments in registers where the type fits into baseline registers, but there are enough args that it gets spilled to the stack instead.
  4. Use non-baseline features to indicate larger types like __m512d can be passed/returned in registers.
  5. Use non-baseline features to indicate more registers are available for passing/returning, e.g. AVX-512 implying 32 registers.

The status quo as of 1.99 https://rust.godbolt.org/z/Exo36YYKd:

  1. __m128 is currently passed indirectly in the Rust ABI
  2. (f64, f64) is returned in registers, something like (f64, f64, f64, f64) is not.
  3. A function that takes 7 int arguments or 9 floats will spill, even though there are free registers.
  4. Not yet possible with the Rust ABI (this RFC), but is the current behavior with extern "C"
  5. Not yet possible (this RFC)

This RFC is proposing new language features that are going to be rather difficult to specify and only help if you opt in. I think it's more useful to work on the first three items than the last two since they can apply to the default Rust ABI, meaning all users benefit, and it doesn't need any language changes.

I don't believe fastcc exposes significantly more registers than the standard ccs - you get a couple more, but AFAIU (from a quick consultation of $LLM) the other biggest difference is that the callee cleans up the stack.

It doesn't implement our ideal ABI at this time, but the description at https://llvm.org/docs/LangRef.html#calling-conventions sounds like its goal fits our needs. If the LLVM maintainers are open to adjusting it to pass and return with more registers, then that's a lot less effort than adding a new ABI.

E.g. I don't see why fastcc couldn't be regcall on x86 and something similar on other platforms.

I'm also happy to just expose more ABIs currently existing in LLVM as a simpler initial approach, if we think that would be better.

While I do think it would be nice to have the preserve_*, regcall, and vectorcall CCs, those are likely to help more with C compat than with performance. We'd still have to check improper_ctypes for example.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

__m128 is currently passed indirectly in the Rust ABI

That's changing with Rust 1.100: rust-lang/rust#161583

@RalfJung RalfJung Oct 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I comment on this below in the RFC, but I have three possible solutions in mind (in order of personal preference):

This is the main difficulty of the RFC and will need language-level work (as in, user-visible error messages). The RFC is IMO quite incomplete without a proposed solution of this issue, as the tradeoffs change significantly depending on the solution that is picked. This question is too central to delegate it to the future as an "unresolved question".

Comment thread text/0000-manyregs-abi.md
An `extern "Rust_manyregs"` function uses as many registers as possible for argument
passing. This can be faster than the standard Rust ABI in some cases.

Unwinding through a "manyregs" function causes termination.

@RalfJung RalfJung Oct 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comes out of nowhere and is only explained later, making the RFC hard to read.

View changes since the review

Comment thread text/0000-manyregs-abi.md
The naming of the ABI string could definitely be improved.

I propose disallowing unwinding through such functions because they are usually
performance-critical and do not unwind, but we could allow unwinding or we could

@nagisa nagisa Oct 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you provide exact technical motivation/examples to warrant this limitation. My own handwavey intuition is that there is going to be little performance impact to functions that would not otherwise unwind anyway and setting up a handler to forcibly abort here can have a negative impact (which we deal with on extern "C" and the likes because it is important for soundness.)

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't actually have a strong opinion here - I had a handwavy feeling that the possibility of unwinding of such functions could produce worse asm, but I don't have any evidence.

Comment thread text/0000-manyregs-abi.md

On x86, `x86_regcallcc` passes up to 16 integer / vector arguments via registers.

The proposed `rust_preserve_none_cc` ABI (https://github.com/rust-lang/rust/issues/151401)

@nagisa nagisa Oct 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How would this CC be implemented differently in the backend compared to rust_preserve_none_cc? LLVM supports this calling convention only for couple of targets. Would manyregs CC also use preserve_none calling convention? What about architectures which don't have implementations for this convention? Should the behaviour of manyregs be a guarantee or can it suffice as a hint?

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't know if a new ABI in this direction could be implemented without adding a new ABI to LLVM - I suspect not.

rust_preserve_none_cc is similar, but different in that it does not allow passing by value any more xmm registers than the standard C convention does.

On x86, the closest equivalent is x86_regcall, but even that convention doesn't use more than 16 XMM registers.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants