Procedures

Whenever we call a function we need to create a data structure to hold its data (called an activation record), on the stack. Three things need to go into this record:

We stack the activation records (so we need a stack pointer). Notice that we can't use the stack pointer for indexed addressing, so we also have to have one of the index registers pointing to the current activation record. This is referred to as a frame pointer.

We have a number of registers and instructions to help us manipulate this stack. First, there is a stack pointer register (sp). This register always points to the next location we'll be pushing to (so it doesn't quite point to the top of the stack). There are a number of instructions that either manipulate or make use of sp. Some of them are

lds,
Load sp
psha, pshb, pshx, pshy
Push a register on the stack
pula, pulb, pulx, puly
Pop a register from the stack
tsx, tsy
Add one to sp and copy it into a register
txs, tys
Subtract one from a register and copy it into sp
jsr, rts
These are very important: jsr pushes a return address and jumps; rts pops a return address and goes where it says

The function call itself is a partnership between the procedure doing the calling, and the procedure being called. Each has to execute code involved in setting up an activation record, transferring control, and deleting the activation record. Of the components of an activation record defined above, we can get a picture for who does what based on asking who knows what.

How do we do this?
TimmyLassie
push parameters
jsr
Push old frame pointer
Use des to reserve space on stack for locals
Use tsx to create frame pointer
Get work done
Use ins to get rid of local variables
Pop any registers we pushed
rts
Use ins to remove parameters from stack

More sophisticated processors will typically have many more registers that might need to be saved and restored as part of the procedure call. HC11 is lucky in this case! Normally a processor and compiler will have a "calling convention", specifying some registers which must be preserved across a procedure call, others which can be corrupted with impunity, and still others that will be used to return results.

An Example

Let's do an example of a program that will compute

int summer(int i)
{
    if (i == 0)
        return i;
    else
        return i + summer(i - 1);
}

OK... Here's how it looks when we translate this code to HC11:

STACK   equ   $00ff
EEPROM  equ   $f800

        org   EEPROM
main    lds   #STACK  * set up stack pointer

        ldaa  #5      * push parameter
        psha
        jsr   summer  * call procedure
        ins           * get rid of param

eloop   bra   eloop
**********************************************
*
* Activation record:        
*   old  <- ix
*    x
*   ret
*   addr
*    i
summer  pshx          * save old frame pointer
        tsx           * set up new frame pointer

        tst   4,x     * see if param is 0
        bne   nonzer  * if not, branch to "else" code

        clra          * if so, we'll return 0
        bra   ret
        
nonzer  ldaa  4,x     * if nonzero param, make a recursive call
        deca 
        psha 
        jsr summer
        ins
*                     * when we return from the recursive call,
*                     * summer(i-1) is in the a accumulator
        adda  4,x     

ret     pulx          * restore old frame pointer
        rts           * and return

        end  main