Procedure Calls
Suppose we have 3 procedures, A, B, and C, where A calls B and B calls C.
100 200 300
216
115 217 320
116
240
A B C
The numbers at the left of the procedure indicate where in memory the instructions are stored. So we are assuming that the code for procedure A begins at address 100, that of procedure B at address 200, and that of procedure C begins at 300. Furthermore, we assume that the instruction call A is at address 115, that of call B at address 216, and the return statements in procedure’s B and C are at 240 and 320, respectively. Notice that when a procedure returns, it returns to the first instruction after the instruction which was the procedure call. Thus, after the return statement in B at line 240 was executed, the next instruction to be done was the instruction at line 116.
Then the problem that people who first wrote function calls was when the return statement in C was executed, how the program would know to go to address 217 and not 116? At the beginning, one could only have one procedure call active and it had to return to the calling procedure. However, it soon became evident that one would want more than one procedure call active.
Before we go on, we must review how machines executed instructions. Inside each CPU there are two registers which the programmer cannot address in their programs, the program counter (PC) and the instruction register (IR). In the von Neumann machine model, the PC stores where the next instruction to be executed is to be found. The IR stores the current instruction being executed. When the machine is turned on, the PC is set to be zero, which is where the booting of the operating system is stored.
Furthermore, the von Neumann
model does the continual loop of
Fetch Instruction
Decode instruction
Execute
instruction
This means, fetch an instruction, decode it, execute it. Then fetch the next instruction, etc. The fetch instruction is done as:
IRßmem[ PC]
PCß PC+1
This means the next instruction to which the PC is pointing to in memory is brought into the CPU and put in the IR, and the PC is incremented to point to the next instruction. (Note that the next instruction is usually more than 1 byte away, but to simplify this, we just assume it is one.)
Thus, just before the execution of the instruction call A, the machine looks like
100 200 300
216
115 217 320
116
240
A B C
PC IR
Then, after the fetch the picture of the machine state is now:
100 200 300
216
115 217 320
116
240
A B C
PC IR
The decoding of the instruction call A is straightforward as everything needed to execute it is in the CPU already. The execution of the instruction is done as follows. To solve the problem of multiple procedure calls the designers of the CPU’s came up with the stack, and a register called the stack pointer (SP). The contents of the SP was the top of the stack. So our picture of the machine now looks like:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
1001
1000 ßtop
Then the execution of the call A was done as follows: the contents of the PC was pushed onto the stack. The address in the instruction in the IR was moved to the PC. Symbolically, we write it as:SP
SP
ß
SP + 1
Mem[SP] ß PC push PC
PCß address in IR
Then the picture of the machine would look like after the execution of the call A:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
116
1001 ß
top
1000
Similarly, after the program executed more instructions and arrived at the PC being 216, meaning that call C was the next instruction to be fetched and executed, the machine would look like:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
116
1001 ßtop
1000
Then the fetch, decode, and execution of the next instruction of call C would just be like the previous one for call B, yielding the snapshot of the machine of:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
217
116
1002
1001 ßtop
1000
The machine continues executing in C and eventually the PC has the value of 320, meaning the next instruction to be executed is return.
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
217
116
1002
1001 ßtop
1000
After the fetch, the machine looks like:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
217
116
1002 ß
top
1001
1000
The decoding of the instruction of return is straightforward, again, as there
is nothing to be brought into the CPU in order to execute it. The execution of
it is to take whatever is at the top of the stack and to put it into the PC ( a pop). Symbolically we write it as
PC ß mem[SP] PC = pop
SP ß SP – 1
Thus our snapshot of the machine will now look like:
100 200 300
216
115 217 320
116
240
A B C
PC IR SP
(217)
116
1002
1001 ßtop
1000
We put the (217) in parenthesis, as it is no longer on the stack, but it still sits in memory address 1002. It will be over-written on the next push statement.
We note that this is a simplified version of the pushing of the address onto the stack, as in fact, much is pushed onto the stack, including the contents of the general purpose registers, the address of the frame pointer (FP), as well as allocation for space for the local variables of the called method.