Stack Frames and Procedure Calls
Stack Frames
To compile procedures, we organize stack into stack frames (activation records)
Each procedure call allocates new stack frame on the stack
When procedure returns, the stack frame is deallocated
This allows us to support recursive procedure calls
In addition to stack pointer SP, it is useful to have frame pointer
- frame pointer points to beginning of stack data for current procedure
- stack pointer points to (current) end of data
Stack pointer and frame pointer allow us to access data for procedure:
… |
argN |
… |
arg1 |
FP: local var 1 |
… |
local var M |
return address |
temporary vars |
saved registers |
call argument N1 |
… |
call argument 1 |
SP: (next frame) |
… |
Example:
void f(int x, int y) { int p; int q; q = 3; int tmp1; // temporary tmp1 = x + y; p = x1 + z; -> g(p + 1, q); ... } int g(int a, int b) { int u, v; int tmp3; tmp3 = a + b; return tmp3; }
Suppose that value of q is kept in register R5 and saved before the call. The stack then looks like this:
… | |
x | |
y | |
FP: | p |
q | |
addr-where-f-should-return | |
tmp1 | |
R5 value (saved) | |
p+1 | |
q | |
SP: | (u will go here) |
(v will go here) | |
no return addr | (tmp3 will go here) |
Calling Conventions
Hardware provides minimal support for procedure calls
One possibility is in Register Machine in Scala:
- jsr saves pc+1 (i.e. pc+instructionLength) into special register e.g. R15
- to return, jump to address in R15
- for nested calls must first save R15 on stack
Calling convention of a programming language determines how calls are compiled
Many calling conventions possible
- but caller (who calls) and callee (who is called) must agree
- if different languages have same calling convention, programs can call each other's procedures
Example above illustrates one calling convention:
- all parameters passed on stack (in specified order)
- result passed on stack
- a non-leaf procedure must save R15
Alternative: parameter and result passed in registers
- must know in which register they are expected
- if register has value useful for future, must save and restore its value around the call
- register calling conventions speed-up leaf procedure calls
What determines the convention:
- where is return address stored
- where to store function arguments and results
- does caller or callee save registers: caller-save and callee-save registers
Code for Return:
- put result on stack or register (depending where it is assigned and on calling conventioon)
- load return address from stack into R15 (unless we are leaf procedure)
- jump to address in R15
Code for Call:
- prepare parameters
- jsr to address
References
- Tiger book, Chapter 6