Module cranelift_codegen::machinst::abi_impl [−][src]
Implementation of a vanilla ABI, shared between several machines. The implementation here assumes that arguments will be passed in registers first, then additional args on the stack; that the stack grows downward, contains a standard frame (return address and frame pointer), and the compiler is otherwise free to allocate space below that with its choice of layout; and that the machine has some notion of caller- and callee-save registers. Most modern machines, e.g. x86-64 and AArch64, should fit this mold and thus both of these backends use this shared implementation.
See the documentation in specific machine backends for the “instantiation” of this generic ABI, i.e., which registers are caller/callee-save, arguments and return values, and any other special requirements.
For now the implementation here assumes a 64-bit machine, but we intend to make this 32/64-bit-generic shortly.
Vanilla ABI
First, arguments and return values are passed in registers up to a certain fixed count, after which they overflow onto the stack. Multiple return values either fit in registers, or are returned in a separate return-value area on the stack, given by a hidden extra parameter.
Note that the exact stack layout is up to us. We settled on the below design based on several requirements. In particular, we need to be able to generate instructions (or instruction sequences) to access arguments, stack slots, and spill slots before we know how many spill slots or clobber-saves there will be, because of our pass structure. We also prefer positive offsets to negative offsets because of an asymmetry in some machines’ addressing modes (e.g., on AArch64, positive offsets have a larger possible range without a long-form sequence to synthesize an arbitrary offset). Finally, it is not allowed to access memory below the current SP value.
We assume that a prologue first pushes the frame pointer (and return address above that, if the machine does not do that in hardware). We set FP to point to this two-word frame record. We store all other frame slots below this two-word frame record, with the stack pointer remaining at or below this fixed frame storage for the rest of the function. We can then access frame storage slots using positive offsets from SP. In order to allow codegen for the latter before knowing how many clobber-saves we have, and also allow it while SP is being adjusted to set up a call, we implement a “nominal SP” tracking feature by which a fixup (distance between actual SP and a “nominal” SP) is known at each instruction.
Stack Layout
The stack looks like:
(high address)
+---------------------------+
| ... |
| stack args |
| (accessed via FP) |
+---------------------------+
SP at function entry -----> | return address |
+---------------------------+
FP after prologue --------> | FP (pushed by prologue) |
+---------------------------+
| ... |
| spill slots |
| (accessed via nominal SP) |
| ... |
| stack slots |
| (accessed via nominal SP) |
nominal SP ---------------> | (alloc'd by prologue) |
+---------------------------+
| ... |
| clobbered callee-saves |
SP at end of prologue ----> | (pushed by prologue) |
+---------------------------+
| [alignment as needed] |
| ... |
| args for call |
SP before making a call --> | (pushed at callsite) |
+---------------------------+
(low address)
Multi-value Returns
Note that we support multi-value returns in two ways. First, we allow for multiple return-value registers. Second, if teh appropriate flag is set, we support the SpiderMonkey Wasm ABI. For details of the multi-value return ABI, see:
https://searchfox.org/mozilla-central/rev/bc3600def806859c31b2c7ac06e3d69271052a89/js/src/wasm/WasmStubs.h#134
In brief:
- Return values are processed in reverse order.
- The first return value in this order (so the last return) goes into the ordinary return register.
- Any further returns go in a struct-return area, allocated upwards (in address order) during the reverse traversal.
- This struct-return area is provided by the caller, and a pointer to its start is passed as an invisible last (extra) argument. Normally the caller will allocate this area on the stack. When we generate calls, we place it just above the on-stack argument area.
- So, for example, a function returning 4 i64’s (v0, v1, v2, v3), with no
formal arguments, would:
- Accept a pointer
P
to the struct return area as a hidden argument in the first argument register on entry. - Return v3 in the one and only return-value register.
- Return v2 in memory at
[P]
. - Return v1 in memory at
[P+8]
. - Return v0 in memory at
[P+16]
.
- Accept a pointer
Structs
ABICalleeImpl | ABI object for a function body. |
ABICallerImpl | ABI object for a callsite. |
Enums
ABIArg | A location for an argument or return value. |
ArgsOrRets | Are we computing information about arguments or return values? Much of the handling is factored out into common routines; this enum allows us to distinguish which case we’re handling. |
CallDest | Destination for a call. |
InstIsSafepoint | Is an instruction returned by an ABI machine-specific backend a safepoint, or not? |
StackAMode | Abstract location for a machine-specific ABI impl to translate into the appropriate addressing mode. |
Traits
ABIMachineSpec | Trait implemented by machine-specific backend to provide information about register assignments and to allow generating the specific instructions for stack loads/saves, prologues/epilogues, etc. |