Assembler Subroutines - rosco-pc/propeller-wiki GitHub Wiki
Because Cog RAM may be modified at run-time, it is not necessary to have a hardware stack to handle subroutine calls within a Cog. A return address for a subroutine call can be placed within a Propeller JMP instruction which will then return execution to after the call. While this simplifies the design of the Cog architecture, it implicitly prevents recursive subroutine calls unless explicit action is taken to prevent return addresses from being overwritten. To use recursive calls will require the user program to implement a software stack for that purpose.
There are two Propeller instructions which are used to implement subroutine calls; JMPRET
and JMP
, usually used in their immediate forms ( where the bottom 9 bits of the instruction represent the address in Cog RAM of where to jump to ).
A JMP #address
instruction will cause Cog execution to continue at the 'address' specified.
A JMPRET retAddress,#address
instruction will cause Cog execution to continue at the 'address' specified but will also place the address of the instruction after JMPRET
into the lower 9-bits of the instruction placed at 'retAddress'. This will normally be a JMP #address
instruction.
A subroutine call therefore consists of a JMPRET
which causes execution to continue with the subroutine code which terminates with a JMP
instruction which will have been modified by the JMPRET
to return to the address after the JMPRET
instruction.
To simplify things for the Cog programmer, the Propeller assembler includes two virtual instructions; CALL
and RET
. The RET
instruction allows the JMP #address
instruction used at the end of the subroutine to be specified without providing an initial address, and the CALL
instruction allows the programmer to specify just the entry point of the subroutine. In this way the implementation of a subroutine call will appear in the source code much as it would for any other processor instruction set, and the implementation aspects are largely hidden from the programmer.
In order that the CALL
instruction can determine where the RET
of the subroutine is it is necessary to give the location of the RET
a label which is the same as the label of the subroutine with '_ret' appended. For example -
Loop CALL #MySub
JMP #Loop
MySub ' Subroutine code here
MySub_ret RET
This is equivalent to -
Loop JMPRET MySub_ret,#MySub
JMP #Loop
MySub ' Subroutine code here
MySub_ret JMP #0
Note that when using JMPRET
, no "#" is used before the specification of the address which holds the RET instruction.
Nested Subroutines
The Cog architecture does not preclude nested subroutines ( where a subroutine can call another subroutine ) as each subroutine has its own return address storage associated with it ( the RET
instruction ). Only recursive subroutines ( where a subroutines calls itself ) will present a problem and require a software stack to preserve return addresses to be constructed.
Arbitrary Subroutine Returns
With processor architectures which include a hardware or software stack it is possible to place a return from subroutine instruction wherever it is required and the execution of those instructions will cause an immediate return from the subroutine. Because the Cog architecture does not include a stack and the return address is placed in a single RET
instruction, a return from subroutine can only be effected by executing that RET
instruction.
Any arbitrary return from a subroutine must be implemented as a JMP
to the RET
instruction. Simply inserting RET
instructions within a subroutine will not have the desired effect and will cause incorrect operation of the Cog program if executed.
Note, an indirect jump (e.g. jmp label_ret
) can be used to avoid having to jump to the end of the subroutine.
Shared Subroutine Return Points
It is fairly common to have subroutines which call another before returning themselves -
Loop CALL #Sub1
JMP #Loop
Sub1 ' Subroutine 1 code here
CALL #Sub2
Sub1_ret RET
Sub2 ' Subroutine 2 code here
Sub2_ret RET
Such subroutines will often be optimised so the initially called subroutine 'falls-through' into the last call and the return from the subroutine at the end of the last called will act as a return from the subroutine in all cases. Such optimisations may be achieved with the Propeller but it is necessary to label the shared subroutine return point for both possible subroutine calls -
Loop CALL #Sub1
JMP #Loop
Sub1 ' Subroutine 1 code here
Sub2 ' Subroutine 2 code here
Sub1_ret
Sub2_ret RET