Let's think a little bit more about procedures and functions, and their implementation. Suppose we have a recursive function to compute a factorial:
int fact(i) {
int retval;
if (i == 1)
retval = i;
else
retval = i*fact(i-1);
return(retval);
}
(as usual, this example is contrived. Recursion is a stupid way to
implement factorial; the only reason I'm using the local variable
retval
is so I need to allocate space for a local
variable on the stack.)
Whenever we call a function we need to create a data structure to hold its data, called an activation record. Three things need to go into this record:
fact
, we have one parameter
(i
) and one local variable (retval
). We'll
see what state-restoring information we need as we go....
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
sp
psha
, pshb
, pshx
, pshy
pula
, pulb
, pulx
, puly
tsx
, tsy
sp
and copy it into a register
txs
, tys
sp
jsr
, rts
jsr
pushes a return
address and jumps; rts
pops a return address and goes
to the return address (loads the PC with it).
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.
a
accumulator can be considered special and use it for all return
values; or we can save space on the stack for the return value before
the call is made, but there is more work involved. Again, you just
must be consistant in your program on which way you are doing it.
Using factorial as an example, how do we do this?
des
instructions or clear an
accumulator and push it a sequencial number of times on the stack.
tsx
ins
instructions.
The actual HC11 code for the factorial function and program follows:
RAM equ $0000
STACK equ $00ff
EEPROM equ $f800
RESET equ $fffe
TVAL equ 5
org RAM
oval rmb 1
org EEPROM
****
* Function Fact
* - computes the factorial of the input
*
****
fact des * local variable
tsx * set the frame pointer
if
ldaa 4,x * read input parameter
cmpa #1 * if (i == 1)
bne else
then
staa 0,x * retval = i
bra ret
else
psha * else {
pshx * save needed registers
deca
psha * put out new parameter fact call
des * save place for return
jsr fact * call fact(i-1)
pulb * get return
ins
pulx * restore registers
pula
mul * retval = i*fact(i-1)
stab 0,x * }
ret ldab 0,x * return( retval )
stab 3,x
ins * remove local variables
rts
****
* Main program
* - test the factorial function
****
main lds #STACK * setup stack for use
ldab #TVAL * let's get factorial for the test value
pshb
des
jsr fact * oval = fact(TVAL)
pulb
stab oval
ins
end bra end
* setup the reset vector
org RESET
fdb main