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). We also need clobber-save registers to be “near” the frame pointer: Windows unwind information requires it to be within 240 bytes of RBP. 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 SP might be adjusted around callsites, we implement a “nominal SP” tracking feature by which a fixup (distance between actual SP and a “nominal” SP) is known at each instruction.

Note that if we ever support dynamic stack-space allocation (for alloca), we will need a way to reference spill slots and stack slots without “nominal SP”, because we will no longer be able to know a static offset from SP to the slots at any particular program point. Probably the best solution at that point will be to revert to using the frame pointer as the reference for all slots, and creating a “nominal FP” synthetic addressing mode (analogous to “nominal SP” today) to allow generating spill/reload and stackslot accesses before we know how large the clobber-saves will be.

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)   |
                             +---------------------------+
                             |          ...              |
                             | clobbered callee-saves    |
unwind-frame base     ---->  | (pushed by prologue)      |
                             +---------------------------+
                             |          ...              |
                             | spill slots               |
                             | (accessed via nominal SP) |
                             |          ...              |
                             | stack slots               |
                             | (accessed via nominal SP) |
nominal SP --------------->  | (alloc'd by prologue)     |
(SP at end of 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].

Structs

ABICalleeImpl

ABI object for a function body.

ABICallerImpl

ABI object for a callsite.

Enums

ABIArg

An ABIArg is composed of one or more parts. This allows for a CLIF-level Value to be passed with its parts in more than one location at the ABI level. For example, a 128-bit integer may be passed in two 64-bit registers, or even a 64-bit register and a 64-bit stack slot, on a 64-bit machine. The number of “parts” should correspond to the number of registers used to store this type according to the machine backend.

ABIArgSlot

A location for (part of) an argument or return value. These “storage slots” are specified for each register-sized part of an argument.

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.