Professional Documents
Culture Documents
Slide Set 10 - Stacks and Register Use
Slide Set 10 - Stacks and Register Use
Slide Set 10 - Stacks and Register Use
1
Allocating stack frame fields to registers
Until now we have assumed that all values in stack are stored in main memory.
● Yes, but we cannot store them permanently in registers since the number of stack
locations far exceeds the number of registers, and is unbounded with recursion.
➢ So typically each function uses registers to store stack locations AS IF ALL
REGISTERS WERE AVAILABLE FOR THE CURRENT FUNCTION.
➢ SHARING of registers across functions is ensured by saving the variables to
memory between functions.
■ This will be discussed next.
2
Inter-procedural register use
Let us say the same register R is used in two functions (f()=caller and g() = callee) to hold different
values.
● Then the variable in f() must be saved to memory at the function call from f() to g(), and restored at
return, so that modifications in callee are transparent to caller.
● Two alternatives for saving and restoring registers:
➢ Caller saves: Save all registers that are live across the call. (a variable is said to be live at a
certain program point, if it is written to before this point in time, and used after this point in time.)
➢ Callee saves: Save all registers that are written to in the callee. (Eg is on next slide)
● The convention that saves fewer memory values is better, because it results in faster code.
● For any one call, either may be better.
● On average, both are about equally good.
● Once we choose one convention, it must be used everywhere in that ISA, otherwise code won’t work.
The register saving convention is usually recommended by the ISA, and must be followed by compilers to
ensure correct execution. 3
Example live ranges
Foo (int x) {
… /* Dead */
v←…
… /* Live */
…←v
… /* Dead */ This shows that instead of allocating variables to registers, the compiler should
v←… allocate live ranges to registers instead. This frees up registers in dead regions.
… /* Live */
…←v
… /* Dead */
Live: A variable is said to be live at a certain program point, if it could be written to in the past, and it’s current 4
value may be read in the future. Otherwise it is dead.
Example of callee saves
Main () {
a = 43;
Printf (a) /* we want 43 to be printed not 9. But will be incorrect w/o register saving & restoring*/
Int c; /* Local variable in foo() also allocated to R3 */ ← we should save the value of c (R3)
to the stack here, in “saved registers”.
c = 9; /* R3 will be assigned to 9 */
return;← we should save the value of R3 to the stack here ← restore value of c (R3) here from
stack 5
}
Example of caller saves
Main () {
a = 43;
← we should save the value of a (R3) to the stack here to local variables (if
memory allocated), else saved registers (if register allocated)
c = 9; /* R3 will be assigned to 9 */
To original memory locations (if the variable was allocated in memory) OR to the "saved registers" field in the
stack frame.
7
So what stack locations can be register allocated?
● Local variables: Yes. Mem location needed only if spilled.
● Arguments: Yes -- the first few fixed number of arguments (e.g., 4). Remaining arguments passed in memory.
Most functions have only a few arguments, so most arguments fit in registers.
➢ Why fix the argument registers in the ISA? Because otherwise separate compilation will not work!
Keep in mind that arguments are neither caller or callee saved, since they are meant for communication.
● Return address: The return address at a call site, if in a link register, need not be saved to stack if callee is a leaf
function.
● Saved registers: These are not a thing to allocate to registers, but the space where registers are saved. 8
Does passing arguments in registers save run-time?
Seeming paradox:
● Some functions do not call other functions (leaf functions) -- there are many such functions!
● Arg a1 may be dead by the time h(z) is called ⇒ no saving needed.
Other reasons in the book are not in syllabus. (those are rare reasons)
Keep in mind that we must ALWAYS pass registers in arguments, regardless of the compiler’s optimization level. This is
because shared dynamic linked libraries (DLLs) expect arguments to always be in one place, not different places for 9
different callers of those DLL functions (like printf())
Example of argument being dead
Foo (int x) {
… /* x is live here */
h(5);
Live: A variable is said to be live at a certain program point, if it has been written to in the past, and
it’s current value will be read in the future. Otherwise it is dead.
10
What if formal argument's address is taken?
Consider the following code: int *f(int x) {
● Solution: if an argument’s address is taken, then write it to memory at the start of f().
This requires that the stack memory space for arguments be allocated even when
arguments are passed in registers, and that stack memory space is not used.
■ If x's address is returned from f(), then memory location is freed when f() exits! This
creates a invalid pointer with a freed address, called a dangling pointer! If this dangling pointer
is dereferenced in the parent of f(), that will result in a segmentation fault error message. 11
Partial solution in C
● All possible callers for f() (maybe all functions) always allocate space for all the outgoing
arguments (including first few) on the stack memory, but do not write anything to those
locations.
➢ Instead the callee writes the arguments to memory only when needed such as when an
argument's address is taken.
This is not a complete solution, since if &x escapes the parent as well (to the grandparent), then it
is still dangling.
12
A better solution: programmer changes program
If we really need to take the address of a formal parameter, the programmer is encouraged to use
call-by-reference to force allocation of the address in the parent procedure.
C and Java are call-by-value languages, which means argument values are passed, not their
addresses.
● In a true call-by-reference languages, the programmer passes the value, but the address is
actually passed by the compiler.
● In C, there is no true call-by-reference, so pointers should be are passed as arguments
instead if we want to simulate call by reference.
➢ This pushes the job of allocating space to the programmer, who must allocate it in the
highest scope that uses that pointer (e.g., the parent or grandparent).
13
Why are variables allocated to memory?
Given that there are so many situations that variables are allocated to registers, it is good to look at
a list of reasons why a variable might be memory allocated:
14
Why are registers faster than memory?
Time of access:
In most programs, we have a very high cache hit rate of over 95%.
Which means, most of the time, memory just takes 1 cycle to access.
● Register: 0 cycles additional for reading registers on top of the 1 cycle used to execute the
subsequent computation instruction that uses the register value.
So effectively, registers are faster than memory, because register reads are “free” in the instruction
that will use the register value. Whereas memory requires a separate instruction like a load or store
to access.
This is why registers are faster than memory locations, and therefore compilers aim to allocate as
many locations to registers as they can. 15