Next: Macro list, Previous: Immediate values, Up: Standard macros
Implementing the underlying architecture's abi is done in the macros that handle function prologs and epilogs and argument passing.
Let's look at the prologs and epilogs first. These are usually pretty simple and, what's more important, with constant content—that is, they always generate exactly the same instruction sequence. Here is an example:
SPARC x86 save %sp, -96, %sp push %ebp push %ebx push %esi push %edi movl %esp, %ebp ... ... ret popl %edi restore popl %esi popl %ebx popl %ebp ret
The registers that are saved (%ebx
, %esi
, %edi
) are
mapped to the V0
through V2
registers in the gnu lightning
instruction set.
Argument passing is more tricky. There are basically three cases1:
In all cases, the port-specific header file will define two variable
for private use—one to be used by the caller during the
prepare
/pusharg
/finish
sequence, one to be used
by the callee, specifically in the jit_prolog
and jit_arg
macros.
Let's look again, this time with more detail, at each of the cases.
jit_finish
is the same as jit_calli
, and is defined
in core-common.h (see Common features supported by core-common.h).
#define jit_prepare_i(numargs) (_jitl.pusharg = _Ro(numargs)) #define jit_pusharg_i(rs) (--_jitl.pusharg, \ MOVrr((rs), _jitl.pusharg))
Remember that arguments pushing takes place in reverse order, thus
giving a pre-decrement (rather than post-increment) in
jit_pusharg_i
.
Here is what happens on the callee's side:
#define jit_arg_c() (_jitl.getarg++) #define jit_getarg_c(rd, ofs) jit_extr_c_i ((rd), (ofs)) #define jit_prolog(numargs) (SAVErir(JIT_SP, -96, JIT_SP), \ _jitl.getarg = _Ri(0))
The jit_arg
macros return nothing more than a register index,
which is then used by the jit_getarg
macros. jit_prolog
resets the counter used by jit_arg
to zero; the numargs
parameter is not used. It is sufficient for jit_leaf
to be a
synonym for jit_prolog
.
jit_arg
will transfer the
argument from the input register to a non-argument register so that
function calls will not clobber it. The prolog and epilog code can then
become unbearably long, up to 20 instructions on the PPC; a common
solution in this case is that of trampolines.
The prolog does nothing more than put the function's actual address in a caller-preserved register and then call the trampoline:
mflr r0 ! grab return address movei r10, trampo_2args ! jump to trampoline mtlr r10 blrl here: mflr r31 ! r31 = address of epilog ...actual code... mtlr r31 ! return to the trampoline blr
In this case, jit_prolog
does use its argument containing the
number of parameters to pick the appropriate trampoline. Here,
trampo_2args
is the address of a trampoline designed for
2-argument functions.
The trampoline executes the prolog code, jumps to the contents of
r10
, and upon return from the subroutine it executes the
epilog code.
jit_pusharg
uses a hardware push operation, which is commonly
available on CISC machines (where this approach is most likely
followed). Since the stack has to be cleaned up after the call,
jit_prepare_i
remembers how many parameters have been put there,
and jit_finish
adjusts the stack pointer after the call.
#define jit_prepare_i(numargs) (_jitl.args += (numargs)) #define jit_pusharg_i(rs) PUSHLr(rs) #define jit_finish(sub) (jit_calli((sub)), \ ADDLir(4 * _jitl.args, JIT_SP), \ _jitl.numargs = 0)
Note the usage of +=
in jit_prepare_i
. This is done
so that one can defer the popping of the arguments that were saved
on the stack (stack pollution). To do so, it is sufficient to
use jit_calli
instead of jit_finish
in all but the
last call.
On the caller's side, arg
returns an offset relative to the
frame pointer, and getarg
loads the argument from the stack:
#define jit_getarg_c(rd, ofs) jit_ldxi_c((rd), _EBP, (ofs)); #define jit_arg_c() ((_jitl.frame += sizeof(int) \ - sizeof(int))
The _jitl.frame
variable is initialized by jit_prolog
with the displacement between the value of the frame pointer
(%ebp
) and the address of the first parameter.
These schemes are the most used, so core-common.h provides a way
to employ them automatically. If you do not define the
jit_getarg_c
macro and its companions, core-common.h will
presume that you intend to pass parameters through either the registers
or the stack.
If you define JIT_FP
, stack-based parameter passing will be
employed and the jit_getarg
macros will be defined like this:
#define jit_getarg_c(reg, ofs) jit_ldxi_c((reg), JIT_FP, (ofs));
In other words, the jit_arg
macros (which are still to be defined
by the platform-specific back-end) shall return an offset into the stack
frame. On the other hand, if you don't define JIT_FP
,
register-based parameter passing will be employed and the jit_arg
macros shall return a register number; in this case, jit_getarg
will be implemented in terms of jit_extr
and jit_movr
operations:
#define jit_getarg_c(reg, ofs) jit_extr_c_i ((reg), (ofs)) #define jit_getarg_i(reg, ofs) jit_movr_i ((reg), (ofs))
[1] For speed and ease of implementation, gnu lightning does not currently support passing some of the parameters on the stack and some in registers.