Let us say I have three functions, f1(), f2(), and f3(). When f1 is called, it stores information in CPU registers (and I imagine there is other important information as well). Now, depending on a condition that is unknown at compile-time, f1 will call either f2 or f3. f2 and f3 use very different registers, some of which may overlap with those used by f1. Is the following reasoning correct?
The compiler knows which registers a particular function needs during its execution. Therefore, when f1 calls either f2 or f3, the function call code preserves those registers that f2 or f3 use on the stack, regardless of whether or not they are being used by f1.
Or is there some other mechanism by which the compiler preserves registers so that the function that is being returned to doesn't lose its data?
I think as others have stated the arguments for a function are typically sent down via a number of registers (thereafter on the stack). Which registers are used depends on the compiler – for
gccsee GNU C/assembler: http://cs.lmu.edu/~ray/notes/gasexamples/A number of principles worth noting:
stack frame
caller (the function calling f1) and callee functions (your f1, f2... functions)
volatile and non-volatile registers. For your question you only don't need to worry about non-volatile registers.
Each function has a stack frame, this is an expandable block of the stack that temporarily stores data that needs to be loaded in and out of registers.
Before each function call (to the callee from the caller) the values you wish to pass down, i.e. your arguments, will be placed in a number of preordained registers (typically 4-6 depending on a the compiler – see link); if there are more arguments than the number of preordained registers then these additional values are stored on the stack (typically the callers stack frame).
If these preordained registers are being used by the caller, then the compiler will push these values onto the caller's stack frame before assigning the arguments to the registers before making the call to the callee (e.g. your f1 function). Once the called function (callee) returns, these values are restored to their respective registers from the stack.
It doesn't matter how or what order a series of functions are called the same system is followed when the compiler converts your C code to assembly/opcode.