Stack Frame for Non-leaf functions
Proof of Concept for Non-Leaf Function
Here the assembler program with the proof of concept
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | # conventions
.equ FP, 1
.equ SP, 2
.equ RET_ADDR, 3
# for the demo
.equ FUNC_ADDR, 4
/*
* function: _start
*/
_start:
ldzwq 0, %SP
// call: main()
ldzwq main, %FUNC_ADDR
subq 8, %SP, %SP # first half: push %RET_ADDR
call %FUNC_ADDR, %RET_ADDR
addq 8, %SP, %SP # second half: pop %RET_ADDR
putc 'r'
putc '\n'
halt %0
/*
* function: main()
*/
main:
movq %RET_ADDR, (%SP) # second half: push %RET_ADDR
// begin of function body
putc 'f'
// call: foo()
ldzwq foo, %FUNC_ADDR
subq 8, %SP, %SP # first half: push %RET_ADDR
call %FUNC_ADDR, %RET_ADDR
addq 8, %SP, %SP # second half: pop %RET_ADDR
putc 'a'
// end of function body
movq (%SP), %RET_ADDR # first half: pop %RET_ADDR
ret %RET_ADDR
/*
* function: foo()
*/
foo:
movq %RET_ADDR, (%SP) # second half: push %RET_ADDR
// begin of function body
putc 'o'
// call: bar()
ldzwq bar, %FUNC_ADDR
subq 8, %SP, %SP # first half: push %RET_ADDR
call %FUNC_ADDR, %RET_ADDR
addq 8, %SP, %SP # second half: pop %RET_ADDR
putc 'b'
// end of function body
movq (%SP), %RET_ADDR # first half: pop %RET_ADDR
ret %RET_ADDR
/*
* function: bar()
*/
bar:
movq %RET_ADDR, (%SP) # second half: push %RET_ADDR
// begin of function body
putc 'o'
// end of function body
movq (%SP), %RET_ADDR # first half: pop %RET_ADDR
ret %RET_ADDR
|
and here a flow chart that combines the call graph with the control flow:
Idea for Using the Stack Implementation
For storing and retrieving return address we use a stack data type. For its implementation the directive
1 | .equ SP, 2
|
specifies that some register %SP is used as stack pointer. At start up the stack pointer gets zero initialized so that it points to the end of the virtual memory:
Typical Stack Operations
The typical operation are push for adding an element, and pop to remove the most recently added element.
Let's assume that this is the current state of the stack
For a push of a register %DATA two operations are required:
-
First the stack pointer gets decremented (first half of the push)
-
Then (second half of the push operation) the data stored at the stack pointer
The pop operation restores a register with the value stored at the top of the frame and also takes this element of the stack.
-
The first half of the pop operation is a fetch from the top of the stack
-
Taking of an element from the stack means to increment the stack pointer. This is done in the second half of the pop operation:
Implementation Specific Operations
On the next page you will see that we will exploit our stack implementation to also access elements that are not at the top of the stack and without removing them from the stack.