Let us example the code from the following program:
Calling a subroutine does not seem difficult at all, as we can use an unconditional branch to continue execution from the caller to the called subroutine (callee). The difficult part is how we return to the caller at the end of the execution of a callee.
At the end of executing a called subroutine, the processor needs to figure out where to continue execution in the caller. Given the instruction set of TTP, this means that location must be remembered somewhere in memory.
A “stack” is a data structure that enforces the LIFO (last-in-first-out) order. As a data structure in C++, a stack is often implemented as a linked list of nodes. However, this is rather inefficient from the perspective of low-level code that is written in assembly.
In assembly language, a stack is implemented by a stack pointer (SP) and an area reserved for the stack. The stack pointer always points to “the last item stored on stack”. This automatically makes the location pointed by the SP also the first item to retrieve due to the LIFO nature of a stack.
Let us assume the stack area is reserved as a static global array called “stack” as follows:
Note how the stack point is declared as a pointer to an element in the stack array.
In TTP, we assume the stack is from location 255 to the last byte available after the program takes up space. We also have to designate a register to be the stack pointer. For the rest of this discussion, we assume register D is the stack pointer.
To facilitate efficient use of instructions, a stack grows “down” instead of up. This means that as more items are added to the stack, the stack pointer moves down instead of up.
Because the SP is assumed to be pointing to the last item stored (also called pushed), the code to store something new is as follows:
To retrieve (also referred to as pop) the most recently stored item that is still on stack and “remove” it at the same time, we can use the following code:
Due to this methods, the proper way to initialize the SP is to make point to the byte that is just one past the end of the stack area:
This seems to cause a problem because it is pointing past the allocated area. However, remember that when we push items on a stack, we first decrement the SP, and as a result, the initial value of SP is never used for dereferencing.
The code to a subroutine has two distinct parts. The first part is to set for the return address. This allows the called subroutine to continue execution in the caller. The second part is to continue execution in the subroutine.
Let us examine the TTP assembly code to do this:
Assuming the return address is stored on the stack, at the end of a subroutine, the following code can be used to return to the caller:
Yes, it will.
The important part is that there is a return address stored on stack for each invocation of a subroutine.