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