Procedures and the Stack

So far the stack has been for storing the return address or saving the contents of registers before calling a procedure. There are some other things we can do with it: One simple answer covers the problems. Each procedure in execution is assigned a stack frame, a fixed-sized block of memory on the stack for parameters, return address, local variables and register storage. When a procedure is called, its stack frame is pushed onto the stack. The routine itself may push and pop temporary storage on the stack. Then if this procedure is calls other procedures, the material for the first procedure will stay on the stack and each other procedure will adds its material to the stack. Then as every procedure terminates it must remove its material off the stack.

The bp (base pointer register points to a fixed location in the middle of the stack from of the currently executing procedure and the stack pointer sp points to the end of the procedure's temporary storage.

Because bp is an index register, the parts of the stack frame can be referenced relative to its contents by adding or subtracting a displacement. (Displacements are always referencing words!) These displacements are going to be the same for each and every use of a particular procedure. That way we we are use age as tht way we we are use age as the third parameter, it will always be [bp + 6]. Additional, the si and di registers can be used.

Another rule is that the bp register always is used with the stack segment (ss) register, ss:bp. Do not modify the ss register, or you will mess up many things!!!!

The calling procedure must push the parameters on the stack before making the procedure call. Then the called procedure must save and reset the bp, reserve space for thelocal variables, save the appropriate registers, and do its work. Then it must restore things in reverse order:

Callee PROC   ; Start of procedure
  push bp
  mov bp, sp
sub sp, n ; reserve n bytes of local storage
push reg1 ; save registers
push reg2
; do some processing
pop reg2
pop reg1
add sp, n ; just the opposite
mov sp, bp sp, bp
pop bp
ret ; we are done.

This results in memory that looks like this:

When working with C functions, some of them have a variable number of parameters, where each time the function can have a different (variable) number of arguments. Printf is an example of this. It looks like:

printf(a1, ..., an); where n is the number of one-word parameters. They are pushed in the order of reverse order that they are encounter: an, ..., a1 After returning from such a call, there will be a statement like: add sp, n; where n is two times the number of parameters.

There are two kinds of parameters, call by value and call by reference. We have been using call by value when we do the push. Since the values are not retained, whatever changes that are made to them are lost. Notice that this is the default for C for everything except for arrays. Since the size of anything being pushed is in units of 16 bits, when the parameter is a character, it is promoted to 16-bits.

Both recusion and reentrancy are also solved with this method, because every call means that a separate frame are being manipulated. Each call becomes independent!

The author points out that it is a poor design to have every procedure do its own I/O. Procedures should be small, simple, and only do one thing. Then libraries can be built up and only the current outer driver has to worry about whether to use the keyboard, CRT, modem, disk file, etc. The inner procedures are independent of the I/O constraints that change with most programs.