Repository navigation
Tracking issue for refactoring the way we represent function call ABIs #119183
Description
This is the issue tracking implementation of rust-lang/compiler-team#672. Note that we do not have a final design yet; the best way to represent call ABI, and to disentangle it from the "storage kind" of a type (which is what the Abi type currently largely represents) is yet to be determined.
Note that for the purposes of this, by "call ABI" I mean "the target-independent information that is necessary and sufficient to compute how arguments and returns values are passed between caller and callee". The perfect end state (that we may or may not ever reach) for this would be to say that two types are ABI compatible if and only if the computed call ABI information for them is the same -- that would be very nice for the spec and for MiniRust, anyway.
I do not mean "the target-specific information saying which arguments are passed in which register / on the stack, which are copied and which are passed indirectly". This already exists to some extend as a concept in rustc, called PassMode. It may need some reforming, but that would be a separate discussion.
- The core need is that a Rust type must have its ABI computed in a manner that is not reimplemented for every target and every codegen backend. e.g. the traversal over types should skip
repr(transparent)in essentially every case. Then every target architecture must be ported to it:- aarch64
- arm
- csky
- loongarch
- loongarch64
- mips
- mips64
- powerpc
- powerpc64
- riscv32
- riscv64
- sparc
- sparc64
- x86
- x86_64
- layout.abi is not about actual ABI, but only about IR codegen: handling the types as SSA values versus handling them as memory locations.
- Rename it Rename
rustc_abi::AbitoBackendRepr#132246 - and decouple it from actual ABI handling
- Rename it Rename
See in particular this comment.
Implementation history
- @workingjubilee: the first few PRs are just going to be moving stuff around so that we can actually co-locate as much of the ABI code as possible:
Activity
bjorn3 commentedon Dec 21, 2023
RalfJung commentedon Dec 21, 2023
RalfJung commentedon May 29, 2024
Looks like fixing #117480 may be blocked on this.
workingjubilee commentedon Oct 16, 2024
I've started work on cleaning up ABI code. The first few PRs are just going to be moving stuff around so that we can actually co-locate as much of the ABI code as possible:
rustc_target::abi#131424{TyAnd,}Layoutcomes home #131473rustc_abi::AbitoBackendRepr#132246rustc_target::spec::abi::Abitorustc_abi::ExternAbi#132385I'll try to implement the MCP proper as I go along.
RalfJung commentedon Oct 16, 2024
Awesome. :-)
If you want feedback on some sketches of what the ABI might look like, feel free to post them here.
Rollup merge of rust-lang#132385 - workingjubilee:move-abi-to-rustc-a…
11 remaining items
workingjubilee commentedon Feb 11, 2025
I am aware of that but it seems functionally identical to generating a new
extern "{CC}" fn(A) -> R, withCC,A, andRbased on but non-identical to the original, and rewriting the call site to call this new function?A related concern that has been pointed out to me in the past is that sometimes, even with inlining, the codegen backend can have problems erasing the prologue and epilogue. Ideally, we would find a way to make "handling the ABI of this call" semantically separable from the actual motion of "making this call", so that we can delay addressing the need for a prologue and epilogue as long as possible. Notionally the function call ABI, after all, should be a non-event, as otherwise these functions aren't very functional.
RalfJung commentedon Feb 11, 2025
We currently talk a lot about registers in our ABI handling, but that's kind of a lie. All it means is "represent this to LLVM as a scalar / array of scalars" and then LLVM decides whether to put that into registers or on the stack depending on the target's conventions. (Well, there's a flag for forcing things to be in a register -- mixed up with other flags that are irrelevant for the ABI. I don't know the exact semantics of that, it's probably target-specific.)
I don't think we should go any more low-level than this on the Rust side. I'd rather not have us be in the business of counting how many argument go in registers vs on the stack. That's a lot of work, very hard to test, and AFAIK none of our codegen backends actually give us that level of control anyway.
The main goal of the original MCP was to make writing our target-specific foreign ABI adjustments easier and less fragile. For that code, the C ABI is exactly the right abstraction, as that's the level the ABI is specified at: we need to tell that code what the given type "looks like as a C type", and then the code needs to compute a corresponding
PassMode. So I think that kind of machinery is definitely on the requirements list here. The MCP talks mostly about how to represent the input to this step; you seem to be thinking mostly about the output? To be honest I have not thought about this much; what we have here currently seems to work well enough for both LLVM and cranelift, so it's not entirely terrible.That kind of work needs to start in the backends though; LLVM / cranelift would require a higher-level way to express the ABI so that they don't require a bunch of instructions that must later be optimized away again (e.g. when inlining). This also points towards a rather higher-level than lower-level abstraction, i.e., not "register vs stack".
workingjubilee commentedon Feb 11, 2025
LLVM does not implement the C ABI for us.
RalfJung commentedon Feb 11, 2025
Correct. We need to carry a function for each supported architecture (sometimes more than one per arch) that are given "the C view of the type" and compute the
PassMode. For this, obviously we should have a way to represent "the C view of the type" so we can use it as input for that function -- right?Regarding the point about multi-register returns, I don't see why we couldn't extend
PassModeto support that. But as noted above that is an entirely orthogonal question to how we represent the input to the ABI adjustment functions, which is what the MCP is primarily about.RalfJung commentedon Feb 11, 2025
I think you are talking about a completely different layer than what I am talking about in the MCP and the issue description. We need two "ABI representations":
fn(TyAndLayout) -> AbiRepr, and crucially it must have the property that ABI-compatible Rust types are mapped to the sameAbiRepr. This function can and should be target-independent.AbiRepris a new concept that does not exist in rustc yet but I think having it would greatly improve the correctness of our foreign ABI adjustments.fn(AbiRepr) -> PassMode. I am surePassModecan be improved, and in factPassMode::Castwill have to be extended as currently it cannot represent everything LLVM can represent and that leads to ABI bugs on some targets. But I don't see which problem would be solved by makingPassModeso low-level that we have to know how many arguments to put in registers; I think (and @bjorn3 has confirmed in the past) that it sits at a pretty good level of abstraction.Currently we have effectively have a function
fn(TyAndLayout) -> PassModefor each foreign ABI (except we express this more likefn(TyAndLayout, &mut PassMode), which is even worse). That's bad as it means ABI adjustments can easily accidentally map two ABI-compatible types to differentPassMode; I fixed a bunch of those bugs last year. This proposal is about factoring that into two functions with an intermediate abstraction,AbiRepr. I think the vocabulary of C types is not a bad template for this. In particular, using C types here does in no way preclude using multiple return registers; that can still be realized by implementing the "Rust" ABI adjustment to e.g. map a 2-elementAbiRepr::Structto aPassModethat represents a multi-register return.I don't know where you went on a different track than my line of thoughts here but clearly we're not talking about the same thing, maybe this helps. :)
workingjubilee commentedon Feb 11, 2025
It simply does not make any sense to me to talk about things in terms of C types if they do not have to follow the C ABI, so I think about these elements in a more decomposed way?
workingjubilee commentedon Feb 11, 2025
I think I will simply talk less about these things because it is not very useful for me to try to record my thoughts in forms that are likely to be misunderstood. I understand what you are saying about your primary hope was to make the input forms more sensical, and I do intend to address that, I just am thinking about this from the origin of the demands... "we want programs that do FFI to successfully execute... which means certain things have to go into certain registers... which means that they have to..." etc.
RalfJung commentedon Feb 11, 2025
We need some sort of high-level language of types (well, higher-level than
PassMode, but lower-level than Rust types) that can serve as input for the target-specific ABI adjustments. I don't care what you call this. I called inAbiReprabove. We don't have to call it "C types" if you don't like that, we can just call it "structs and unions and leaf types" if you prefer 🤷 .That would make sense if we were emitting the assembly ourselves, but we are not. We are targeting the language of our backends in terms of how they represent ABI.
PassModeis designed around that. It works well enough; the ABI issues we have seen have (almost all) not been caused byPassModebeing inadequate. They have been caused by the fact that the ABI adjustments take as input a Rust type and that makes it extremely easy to map different-but-defined-to-be-ABI-compatible Rust types to differentPassMode. We need a normalization pass that runs before invoking the target-specific logic, so that we only have to worry once about mapping different-but-defined-to-be-ABI-compatible Rust types to identical outputs, rather than having to do that for every single target. That is what this issue attempts to fix. I'm not sure what you are trying to fix but maybe it is something else. :)RalfJung commentedon Feb 11, 2025
Maybe I once again fell in the trap of "call ABI" meaning like a dozen different things to different people. For the purposes of this discussion, by "call ABI" I mean "the target-independent information that is necessary and sufficient to compute how arguments and returns values are passed between caller and callee". The perfect end state (that we may or may not ever reach) for this would be to say that two types are ABI compatible if and only if the computed call ABI information for them is the same -- that would be very nice for the spec and for MiniRust, anyway.
The process of mapping that information to the concrete things you talk about (registers vs stack etc) is obviously highly target- and ABI-specific. But the input to that process seems to me to be expressible in a nice reasonably high-level target-specific way.
bjorn3 commentedon Feb 11, 2025
@RalfJung
In fact we can already represent 4 register returns on 64bit and 8 register returns on 32bit through
PassMode::Pair(i128, i128)And I think even more usingPassMode::Cast. LLVM will make up an ABI when you do however and in many cases this made up ABI returns the return value using a return area pointer rather than in registers.@workingjubilee
For lowering the C ABI we very much have to first map Rust types into C types as the C ABI is specified as a mapping from C types to registers and stack locations. Some of the ABI issues we have are precisely because the ABI implementation for each architecture does an implicit ad-hoc mapping from Rust to C types rather than sharing this such that we only have to get it right once. The Rust ABI follows a different code path and as such doesn't necessarily have to use this lowering to C types. It could keep directly operating on Rust types if we want.
workingjubilee commentedon Feb 11, 2025
And we also have targets with two C-like ABIs.
RalfJung commentedon Feb 11, 2025
RalfJung commentedon Feb 11, 2025
RalfJung commentedon Sep 8, 2026
FWIW it's not just all the ABI adjustments that have to duplicate similar logic here. There are other parts of the compiler that also need effective ABI information and that are duplicating parts of the same logic, often incorrectly: