Procedures and functions
For passing arguments and receiving return values we also can use the stack. The calling convention basically just gets extended to specify where in the stack these values will be stored.
Procedures
We just consider the case that a procedure receives two arguments arg0 and arg1 and both have the size of a quad word. Then the callee expects the following stack in its prologue:
Calling a procedure
This means for calling such a procedure the caller has to reserve space on the stack so that the callee can save the frame pointer and store its return address. Furthermore, the stack contains the arguments at known offsets relative to the stack pointer. So for example, in the code fragment
1 2 3 4 5 6 7 8 | subq 4*8, %SP, %SP
ldzwq 1, %4
movq %4, 16(%SP) // store arg0
ldzwq 2, %4
movq %4, 24(%SP) // store arg1
ldzwq proc_foo, %4
jmp %4, %RET
addq 4*8, %SP, %SP
|
the procedure call proc_foo(1, 2) gets described. Using the directives
1 2 | .equ proc_arg0, 16
.equ proc_arg1, proc_arg0+8
|
this can be rewritten more readable as
1 2 3 4 5 6 7 8 | subq 4*8, %SP, %SP
ldzwq 1, %4
movq %4, proc_arg0(%SP) // store arg0
ldzwq 2, %4
movq %4, proc_arg1(%SP) // store arg1
ldzwq proc_foo, %4
jmp %4, %RET
addq 4*8, %SP, %SP
|
Implementation of a procedure
The prologue and epilogue are the same a for subprograms. Assume the procedure uses two local variables local0 and local1 then after the prologue
1 2 3 4 5 | // function prologue (with 2 local variables, each 8 bytes)
movq %RET, ret(%SP)
movq %FP, fp(%SP)
addq 0, %SP, %FP
subq 2*8, %SP, %SP
|
the stack can be described by
Hence, using the above directives the callee can access the arguments by the memory locations arg0(%FP) and arg1(%FP), i.e. compared to the caller the offsets are after the prologue relative to the frame pointer instead of the stack pointer:
Using the directives
1 2 3 4 5 6 7 8 9 | .equ ret, 0
.equ fp, 8
.equ proc_arg0, 16
.equ proc_arg1, proc_arg0+8
.equ local0, -8
.equ local1, local0-8
|
all relevant memory locations can be accessed relative to the frame pointer in a uniform way:
Functions
Functions receive arguments like procedures but in addition need also a memory location on the stack to give back a return value. Again, we first consider a function that receives just two arguments. Then the callee expects in its prologue the following stack:
Calling a function
Compared to calling a procedure the caller has to receive 8 more bytes for the return value, and furthermore the displacements for the arguments have to be adapted. So for example, in the code fragment
1 2 3 4 5 6 7 8 | subq 5*8, %SP, %SP
ldzwq 1, %4
movq %4, 24(%SP) // store arg0
ldzwq 2, %4
movq %4, 32(%SP) // store arg1
ldzwq func_foo, %4
jmp %4, %RET
addq 5*8, %SP, %SP
|
the fucntion call func_foo(1, 2) gets described. Using the directives
1 2 | .equ func_arg0, 24
.equ func_arg1, func_arg0+8
|
this can be rewritten more readable as
1 2 3 4 5 6 7 8 | subq 4*8, %SP, %SP
ldzwq 1, %4
movq %4, func_arg0(%SP) // store arg0
ldzwq 2, %4
movq %4, func_arg1(%SP) // store arg1
ldzwq func_foo, %4
jmp %4, %RET
addq 4*8, %SP, %SP
|
Implementation of a function
The prologue and epilogue are the same a for subprograms or procedure. Assume the function uses two local variables local0 and local1 then after the prologue
1 2 3 4 5 | // function prologue (with 2 local variables, each 8 bytes)
movq %RET, ret(%SP)
movq %FP, fp(%SP)
addq 0, %SP, %FP
subq 2*8, %SP, %SP
|
the stack can be described by
Using the directives
1 2 3 4 5 6 7 8 9 10 | .equ ret, 0
.equ fp, 8
.equ rval, 16
.equ func_arg0, 16
.equ func_arg1, proc_arg0+8
.equ local0, -8
.equ local1, local0-8
|
as before all relevant memory locations can be accessed relative to the frame pointer in the same uniform way
Recipe for the general calling convention
From the above a general pattern for calling and implementing functions can be derived and provided in a recipe style. However, this will just cover the case that all arguments have the size of 8 bytes. This in particular avoids the need to incorporate alignment restrictions. Other limitations of the recipe are mentioned in the description.
Directives for accessing memory locations
For calling and implementing functions we can use and extend the following directives:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .equ ret, 0
.equ fp, 8
.equ proc_arg0, 16
.equ proc_arg1, proc_arg0+8
/*
add proc_arg2, proc_arg3, etc. as needed
*/
.equ func_arg0, 24
.equ func_arg1, func_arg0+8
/*
add func_arg2, func_arg3, etc. as needed
*/
.equ local0, -8
.equ local1, local0-8
/*
add local2, local2, etc. as needed
*/
|
As the displacement is encoded with a single byte this pattern “only” works if the number of arguments or local variables does not exceed 32. The ULM C compiler can deal with such cases.
Calling functions and procedures
Calling a procedure
Replace NUM_ARGS with the number of arguments, PROC_LABEL with the name of the procedure, and replace %4 with an available register in your context:
1 2 3 4 5 6 7 8 9 10 | subq (NUM_ARGS+2)*8, %SP, %SP
// load arg0 in %4
movq %4, proc_arg0(%SP) // store arg0
/*
store further arguments arg1, arg2, etc as needed
*/
ldzwq PROC_LABEL, %4
jmp %4, %RET
addq (NUM_ARGS+2)*8, %SP, %SP
|
Calling a function
Like for procedures just replace NUM_ARGS and PROC_LABEL below. Using this code fragment the return value will be afterwards stored in %4 (which can be replaced by any available register in you context):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | subq (NUM_ARGS+3)*8, %SP, %SP
// load arg0 in %4
movq %4, func_arg0(%SP) // store arg0
/*
store further arguments arg1, arg2, etc as needed
*/
ldzwq PROC_LABEL, %4
jmp %4, %RET
// fetch return value in %4
movq rval(%SP), %4
addq (NUM_ARGS+3)*8, %SP, %SP
|
Implementing functions and procedures
Skeleton
In the following skeleton replace FUNC_OR_PROC_LABEL with the function or procedure name, and replace NUM_LOCALS with the number of local variables:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | FUNC_OR_PROC_LABEL:
// function prologue
movq %RET, ret(%SP)
movq %FP, fp(%SP)
addq 0, %SP, %FP
// reserve space for local variables.
subq NUM_LOCALS*8, %SP, %SP
// begin of the function body
/*
Implementation of the function or procedure
*/
// end of the function body
// function epilogue
addq 0, %FP, %SP
movq fp(%SP), %FP
movq ret(%SP), %RET
jmp %RET, %0
|
Directives for argument names and local variables
You don't want to use names like proc_arg0 for the arguments your procedure receives or local0 for its local variables? No problem, use directives for a more suitable naming , e.g.
1 2 3 4 5 | .equ a, proc_arg0
.equ b, proc_arg1
.equ i, local0
.equ j, local1
|
or analogously for functions, e.g.
1 2 3 4 5 | .equ a, func_arg0
.equ b, func_arg1
.equ i, local0
.equ j, local1
|