Properties of the call conventions
The function call conventions form the PIC32 are described in Chapter 12 of the MPLAB® XC32 C/C++ Compiler User’s Guide.
Microsoft Developer Network also has a pretty good page for the MIPS Calling Sequence Specification for Visual Studio 2005.
Register usage
- Registers
$a0
to$a3
are used for passing arguments to functions. - Registers
$v0
to$v1
are used for returning values from functions. - Registers
$t0
to$t9
are caller saved. The caller function assumes that the values of these registers will be changed during the call. - Registers
$s0
to$s7
are callee saved (or called saved). The caller function assumes that the value of these registers will not be changed during the call. If the called function modifies any of these regisers, the called function must restore its original value. - Registers
$a0
to$a3
and$v0
to$v1
are effectively caller saved. - Register
$fp
is the frame pointer. - Register
$sp
is the stack pointer. - Register
$ra
is the return address.
Parameter passing
If possible, function arguments are passed in registers
$a0
to $a3
.
If the arguments cannot fit in these four registers, they are
placed on the stack.
Space is allocated for all arguments, including those passed in registers, on the stack. If a argument is passed in a register, its space on the stack will be uninitialized.
“Small” arguments will not share a
register: A char
gets an entire 32-bit register.
If the returned value is a large struct
,
the calling function allocates space for this structure and passes
its address in $a0
.
This is a messy feature of C.
Stack frame
When programs are compiled under the XC32 compiler, the default
action for function invocation is to allocate a stack frame,
even though this may not be needed for leaf functions.
The frame pointer, the
address of the base of the stack frame, is contained in $fp
.
(Leaf functions are allowed to use $fp
as a called-saved
register.)
Stacks grow download in memory, toward lower addresses. The stack pointer always points to the lowest allocated slot of the stack. Generally, the stack pointer and frame pointer are the same, but the stack pointer may grow temporarily to allocate space for “temporaries.”
The PIC32 stack frame is relatively simple. Here is the order for storing information on the stack.
The return address — $ra |
Callee-saved registers, starting with $fp |
Local variables |
Space for arguments for called functions |
By the way, the slot where the frame pointer is stored is sometimes called the dynamic link because it is part of a linked list of stack frames.
Local variables, saved registers, and parameters stored on the stack
are accessed at fixed offsets from the frame pointer. These means that
all local variable references will be of the form
offset($fp)
.
Actions of the called function
Function prologue
We’re going to present the prologue as it must be performed for stem functions, functions which call other functions. Life may be easier for leaf functions.
On entry to the function, space for all the function’s arguments has already been allocated at the top of the stack, even though some of those arguments are passed in registers.
The called function will adjust $sp
to allocate
space for the new stack frame and then store the present
return address, $ra
, and frame pointer, $fp
on the stack.
The frame pointer is then set to the same address as the stack pointer.
The code looks a bit like this when a frame of
N
is allocated:
addiu $sp,$sp,-N # adjust stack for N-byte frame sw $ra,N-4($sp) # save return address on stack sw $fp,N-8$(sp) # save return address on stack add $fp,$sp,$zero # set frame pointer to present stack pointer
At this point, other registers may be saved in the stack frame. Arguments that were passed in registers may also be saved in slots allocated for them in the caller’s stack frame.
Inside the function
All local variables stored in the stack frame
will be accessed by positive offsets from $fp
, the frame pointer.
Most parameters will be accessed in the registers in which they were
passed. Some will be accessed at offsets from the frame pointer.
The stack pointer may diverge from the frame pointer as the function executes.
Function epilogue
Before the function exits, any modified callee-saved registers must be restored and return values must be placed in appropriate registers.
Then the frame pointer is copied to the stack pointer.
At that point, the return address and previous frame pointer are restored
from the stack frame and the stack pointer is incremented to
pop the stack frame.
Finally, a jr
instruction is used to return to the caller.
It looks a bit like this:
add $sp,$fp,$zero # Copy frame pointer to stack pointer lw $ra,N-4($sp) # restore return address lw $fp,N-8($sp) # restore frame pointer addiu $sp,$sp,N # pop stack frame jr $ra # return
Actions of the calling function
Function invocation
Before the function is called, all its arguments must be evaluated and placed in the appropriate registers or stack locations. Generally the stack frame was created with enough space to hold arguments for future calls.
Now a jump-and-link instruction, with the called function as its
target, will be made.
The jump-and-link will store the return address, PC
+8,
into $ra
and then transfer to the target function.
The function will also need to save copies of any caller-saved registers it needs after the call is completed.
Function return
If any parameters were passed on the stack, the calling function should adjust the stack pointer to “remove” them. This is rare.
If values were returned, the calling function will find them
in register $v0
and, for large returned values, $v1
.
The calling function may need to restore any caller-saved
registers it was using.
An example
Let’s look at an example, a very inefficient recursive function for squaring a positive number.
int square(int n) { int r = 0 ; if (n != 0) { r = square(n-1) + n + n - 1 ; } return r ; }
Implemented on the PIC32
Here’s some assembly code that was instructor-optimized from the output of the xc32 compiler.
.align 2 .globl square .ent square square: .frame $fp,32,$ra .mask 0xc0000000,-4 # prologue addiu $sp,$sp,-32 sw $ra,28($sp) sw $fp,24($sp) add $fp,$sp,$zero # put n in its place sw $a0,32($fp) # r = 0 sw $zero,16($fp) # if (n != 0) beq $a0,$zero,retPnt nop # invocation: square(n-1) addiu $a0,$a0,-1 jal square nop # return: square(n-1) add $t1,$v0,$zero # r = square(n-1) + n + n + 1 lw $a0,32($fp) addu $t1,$v0,$a0 addu $t1,$t1,$a0 addiu $t1,$t1,-1 sw $t1,16($fp) retPnt: # return r lw $v0,16($fp) # epilogue add $sp,$fp,$zero lw $ra,28($sp) lw $fp,24($sp) addiu $sp,$sp,32 j $ra nop .end square .size square, .-square
The calls — before the prologue
n | Registers | Stack |
---|---|---|
3 | ||
2 | ||
1 | ||
0 |
The calls — before the epilogue
n | Registers | Stack |
---|---|---|
0 | ||
1 | ||
2 | ||
3 |
The calls — all done
n | Registers | Stack |
---|---|---|
0 |
Implementing Java methods
If you put the keyword static
in front of the
square
function header, you get a Java static method.
Here is the
JVM bytecode
for that method.
When this method is running,
local variable
0 is parameter n
and local variable 1 is
r
.
The operand stack is shown to the right of the JVM instructions.
Also, square
is static method 2 of the class.
static int square(int); Code: 0: iconst_0 // [[ 0 1: istore_1 // [[ 2: iload_0 // [[ n 3: ifeq 19 // [[ 6: iload_0 // [[ n 7: iconst_1 // [[ n 1 8: isub // [[ n-1 9: invokestatic #2 // [[ sq(n-1) 12: iload_0 // [[ sq(n-1) n 13: iadd // [[ sq(n-1)+n 14: iload_0 // [[ sq(n-1)+n n 15: iadd // [[ sq(n-1)+n+n 16: iconst_1 // [[ sq(n-1)+n+n 1 17: isub // [[ sq(n-1)+n+n-1 18: istore_1 // [[ 19: iload_1 // [[ r 20: ireturn
Chapter 6 of the JVM specification contains a detailed description of the Java Virtual Machine instruction set.
Calling main
We do need some assembly language code to get a C program running.
Typical C startup with crt0
on a multi-user system
- Initialize the stack
- Initialize global variables
- Initialized variables are read or mapped from the executable
- Uninitialized global variables are cleared as required by the C standard
- Create links to shared libraries
- May be done by the operating system
- May be delayed until libraries are used
- Get ready to call
main
- Set up memory management for the heap
- Allocate space for the stack
- Create the initial stack frame
- Set up the parameters
- Call
main
- Return to operating system after the call
Startup for calling main
on the PIC
If the PIC assembly language programmer does not provide a
__reset
entry point, the following actions will
be performed to call main
:
- The stack pointer and global pointer are initialized.
- Read-only data, contained in program memory, is mapped into data memory.
- Unitialized data is cleared.
- Initialized data is initialized using a structure created by the linker.
- For C++, static objects are initialized.
main
is called.- If
main
returns, the processor is reset.
This list is a summary of a more detailed description that can be found in Chapter 14 of the in the MPLAB® XC32 C/C++ Compiler User’s Guide.
The mainframe and the PC
Implemented on the IBM/360
Mark Smotherman of Clemson University is an expert on the history of computer architecture. His page on IBM S/360 Subroutines describes a calling convention that uses a linked list rather than a stack.
Intel architectures
- x86-64 Machine Language Programming by Randy Bryant and David O’Hallaron
- Machine-Level Programming — Bryant & O’Hallaron notes
- Machine-Level Programming revised