Repository navigation
Conversation
| 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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
_mm512or 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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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):
- Pass types that fit in baseline registers in registers.
- Return more values in registers.
- 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.
- Use non-baseline features to indicate larger types like
__m512dcan be passed/returned in registers. - 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:
__m128is currently passed indirectly in the Rust ABI(f64, f64)is returned in registers, something like(f64, f64, f64, f64)is not.- A function that takes 7 int arguments or 9 floats will spill, even though there are free registers.
- Not yet possible with the Rust ABI (this RFC), but is the current behavior with
extern "C" - 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
fastccexposes 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.
There was a problem hiding this comment.
__m128 is currently passed indirectly in the Rust ABI
That's changing with Rust 1.100: rust-lang/rust#161583
There was a problem hiding this comment.
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".
| 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. |
There was a problem hiding this comment.
This comes out of nowhere and is only explained later, making the RFC hard to read.
| 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 |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
|
|
||
| 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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Add a new ABI that tries to pass as many arguments / return values in registers as possible.
Rendered