CS 202 - Notes 2016-03-25

Procedures in assembly

Basic concerns:

The stack is at the heart of how we address most of these needs

If a function needs to save a value, it pushes it onto the stack. Memory access can then be made relative to the stack pointer. Since the function is active, everything it is concerned about is at the top of the stack. We refer to the block of memory the function is in control of through the stack as its stack frame.

Control flow

call *label*
To jump to a function called label, we use this instruction. It pushes the current contents of the program counter onto the stack and then jumps to the instruction at label.
ret
Pops the value from the top of the stack into the program counter.

These two instructions allow us to call a function and then return to the exact position in the code where the call was made. The assumption, of course, is that the return address is at the top of the stack when ret is issued.

Data management

Arguments are passed in registers. You will see that our diagram of the registers lists which register corresponds to which argument.

If we need more than six arguments, then we resort to storing the arguments on the stack. The caller of a function needs to push these arguments onto the stack before issuing call. The arguments are pushed on in reverse order so that the callee knows where to find them. Thus, argument 7 should be at %rsp + 8 (one quad word before the return address), argument 8 at %rsp + 16, etc…

The return value of a function is expected to be found in %rax.

Memory management

Local variables are stored either in memory (on the stack) or in a register.

The registers are preferred, because they are faster, but we have a limited number of them, and they can’t store structures (like strings or arrays). We also need to use memory if we ever dereference the variable using &, because we need a real address for it.

Local variables that are stored on the stack get popped off at the end of the function. This provides some of the variable scope – the local variables of a function are just not available any more. This is why we should never return the address of a string or array that was not dynamically allocated from a function.

One other problem of using registers for local variables is that if we call another function, we don’t want the values we stored in the registers to all be changed when the function returns. You will see that some registers are listed as being “caller saved” and some registers are listed as “callee saved”. This refers to who has the responsibility for making sure that registers have the same value across a function call.

If we are writing a function, we can use any register marked “caller saved”. The assumption being that if there was anything important in them whichever function called our function saved them before making the call. On the other hand, we have to assume that anything in a “callee saved” register is important and we need to save it before we use it and restore it back to the original state when we are done.

Stack discipline

Stack discipline refers to our standard practices in manipulating the stack so that it doesn’t get corrupt. As an example, imagine we have two functions F1 and F2. F1 would like to call F2.

Note that all stack operations are balanced – any push is balanaced by a pop. Following this discipline means that functions have the appearance of doing nothing more than placing a value in %rax and the stack is always in a valid state.