While writing some code to demonstrate 9900 assembly to my students I ran into the situation where my code needs to be readable and understandable. One of the easiest ways to accomplish this is to use functions (routines/subroutines) to break up and re-use code.
Of course, the use of functions involves a choice in calling conventions. On the 9900 we have several possibilities, for example those based on BLWP/RTWP or BL/B *R11. Apart from calling to and returning from a routine a decision should be made about passing of parameters. Inspired by other architectures (for example Sun/Sparc) I created a calling convention which is easy to use and has a lot of advantages:
· Recursion is possible
· Routine and subroutine pass parameters through registers
· Each incarnation has its own free registers, no more implicit saving/restoring registers
· Each incarnation has a number of scratch registers
· Calling sequence is just 2 words
· No implicit stack administration needed in routines
The basic idea behind this convention is to use overlapping workspaces: the routine and the subroutine share half of their workspace. So, there is a ‘stack of workspaces’ (growing from hi addresses to lo addresses). Each incarnation will use 16 bytes. Although this seems to be a lot, the total amount of memory used for the system depends on the depth of nested subroutines, which is in most applications really limited. The following table illustrates the idea:
When a function needs temporary register storage it can freely use R5, R6 and R7. However, their content will be destroyed as soon as the function calls another function. Parameters to the function are in R8 up to and including R12. Free (and persistent through calls) registers are R0 up to and including R4 which are also used to pass parameters to a subfunction.
To call a function, I wrote some code that handles all the administration through the use of the XOP instruction. So my assembler will rewrite:
call routine
to
xop routine,1
Which takes the same amount of instruction space as BL (2 words)
The routine itself will return to the caller using a standard RTWP instruction. Note that you can manipulate R15 before returning to signify an error condition (for example by setting the parity flag).
The XOP1 handler looks like this (a nice puzzle to see what is going on)
xop1:
mov r13,10(r13)
mov r14,12(r13)
mov r15,14(r13)
mov r11,r14
ai r13,-16
rtwp
Funny that the ending rtwp is actually calling the routine!
Wonder what you think of this.