Bird: Introduction to stack operations
Bird adds four instructions to the basic reptile design: push, pop, call and return. All these instructions use stack.
The calling convention is a protocol about how a function must be called from the main program and how the values are to be returned from the routine to the main program. The basic issues are:
- How arguments are passed to a subroutine?
- How local variables of the subroutine are allocated?
- How registers of the calling program are saved?.
- How a value is returned from the routine back to the calling function?
The calling convention is broken into two sets of rules. The first set of rules is employed by the caller of the subroutine, and the second set of rules is observed by the writer of the subroutine (the callee). It should be emphasized that mistakes in the observance of these rules quickly result in fatal program errors since the stack will be left in an inconsistent state; thus meticulous care should be used when implementing the call convention in your own subroutines.
We will first describe the C calling convention. Then we will describe the calling convention that we’ll use.
C calling conventions: Caller (ie, the main program) side
Before calling a subroutine, the caller should save the contents of certain registers that are designated “caller saved” by pushing them to stack, if he relies on their values after the subroutine returns. In x86 microprocessor architecture these registers are EAX, ECX and EDX. It is assumed that the subroutine will use these registers without saving their values.
The calling program pushes the parameters into the stack in an inverse order (ie, the last one first). This inversion of parameters was historically used to allow functions to be passed a variable number of parameters.
Jump to the subroutine by using the call instruction. This will push the return address onto the stack..
When the function returns, the calling program expects to find the return value at register EAX.
The parameters that are pushed to stack at step 2 are still on the stack. The calling program discards them by adding an appropriate number to SP (stack pointer).
The calling program pops the registers EAX, ECX and EDX, if he pushed any one of these at step 1.
C calling conventions: Calle (ie, the subroutine) side
Note that as the subroutine starts to run, it finds the stack in the following configuration:
- At the top of the stack there is the return address
- Under it, there are parameters
- Under it, there is the contents of the registers EAX, ECX and EDX, if the calling program pushed them.
When the subroutine starts executing, it does three things:
STEP 1: The first thing the subroutine do is to push the register EBP onto the stack (and hence save its contents), and then copy the value of ESP into EBP by the following instructions:
mov ebp, esp
This two instructions establish a “base pointer”. We will explain the purpose of base pointer below.
STEP 2: Allocate the local variables in the stack by substracting the required number from the sp register.
STEP 3:The registers that are to be used by the subroutine are pushed to the stack to save their values, except EAX, ECX, EDX, which are called by the saver, and EBP, which is saved in STEP 1 above.
Then the subroutine code runs. The code must reach the local variables and arguments, and here the “base pointer” comes into play.