OSU CSE 2421
calling functions, caller/callee saved registers
J.E.Jones
OSU CSE 2421
stdlinux is running hundreds of processes for hundreds of different people
Everyone doesn’t get a private CPU, so how does this work?
◦ When considering multiple processes (executables), the operating system takes care of this
◦ Uses a construct called a Process Control Block (PCB) and a boatload of code
◦ This is a topic that CSE 2431 (Systems 2) addresses
◦ For now, you can consider this part of the puzzle to be magic
(next semester, not so much)
J. E. Jones
OSU CSE 2421
%rax %eax %ax %r8
%r8d %r8w
%rbx %ebx %bx %r9
%r9d %r9w
%rcx %ecx %cx %r10
%r10d %r10w
%rdx %edx %dx %r11
%r11d %r11w
%rsi %esi %si %r12
%r12d %r12w %r13d %r13w
%rdi %edi %di %r13
%rsp %esp %sp %r14
%r14d %r14w %r15d %r15w
%rbp %ebp %bp %r15
◦ Can reference low-order 4 bytes (also low-order 1 & 2 bytes)
• *See Figure 3.2, page 180 of Bryant/O’Halloran for 1-byte register names
J. E. Jones
OSU CSE 2421
We’ve discussed how to separate space on the stack for each function (within a specific process) using stack frames
If main(), or some other function, fills many (all?) of the 14* integer registers with valid data, then calls another function, what happens to that data?
Coordinating/Organizing this part of the problem is our responsibility rather than the OS’s.
What registers can the called function use to perform its work?
What to do? What to do?
*%rbp and %rsp are always used for the stack
J. E. Jones
OSU CSE 2421
1. The function that is performing the call has to save every, single register it’s using to the stack prior to making the call, then pop them back into the appropriate registers upon return.
2. The function that is called has to save every, single register it plans to use to the stack prior to doing any “real” work, then pop the values back into the correct registers before returning to the calling function.
Both seem a little harsh! Can’t we both just get along??? How about a little cooperation?
J. E. Jones
OSU CSE 2421
Although only one procedure can be active at a given time, the 16 registers are “shared” by all functions.
Therefore, we need a way to ensure that when one function (the caller) calls another (the callee), values that the caller needs after return will not be overwritten.
To ensure this, conventions have been adopted as to which function, the caller or callee, is responsible for preserving a given register (other than %rsp).
We must use the register save conventions used by X86-64 in a C programming environment.
J. E. Jones
OSU CSE 2421
Register Allocation
What can be used?
When do you save?
Linux uses what is call System V ABI to define this, in addition to many other things…most of which we won’t need to address this semester.
J. E. Jones
OSU CSE 2421
What is System V ABI? – a “bible” of sorts with respect to how to interface to C standard libraries when not using C code….X86-64, for example.
Here is the link to a reasonable “draft” copy from 2013: https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
A bug occurs, sometimes, if you try to click on this link, it doesn’t work. If you copy/paste the link in a browser window it consistently comes up just fine.
There are many interface standards within the ABI, register usage and caller/callee parameters are just a couple…
J. E. Jones
OSU CSE 2421
The first six integer or pointer arguments are passed in registers %rdi, %rsi, %rdx, %rcx, %r8, %r9 – in this order.
%rax is used for return values
%rsp must be restored when control is returned to the caller
function
If the callee wishes to use registers %rbx, %rbp or %r12- %r15, the callee must save/then restore their original values before returning control to the caller.
◦ %rbx, %rbp, and %r12 through %r15 are callee saved registers
All other registers must be saved by the caller if the caller wishes to preserve their values
◦ All other registers are caller saved registers
J. E. Jones
OSU CSE 2421
Caller Saved Registers: Registers for which the Caller function is responsible.
◦ IF the register contains data needed by the Caller after the Callee function returns, the Caller function must preserve them by pushing them to the stack prior to calling the Callee function,. Caller then pops them from the stack after Callee returns.
Callee Saved Registers: Registers for which the Callee function is responsible.
◦ IF the Callee function wishes to use these registers , the Callee function must push them to the stack prior to using them. Callee must assume there is data in each of these registers that is important to the Caller function. Callee function must pop these registers back prior to returning to the Caller.
Passing Parameters: The first 6 parameters are passed from the Caller to the Callee in registers %rdi, %rsi, %rdx, %rcx, %r8 and %r9, respectively. (We won’t address passing more than 6 parameters in this class.)
◦ Caller must assume Callee will trash all values in these registers prior to return.
◦ So, if Caller needs any of the parameters after the call, must push registers to stack before call
and pop back after call
Return Value: If the Callee function returns a value to the Caller, it is returned in
register %rax.
Confused? Check out Figure 3.2, p 180 of Bryant/O’Halloran
J. E. Jones
OSU CSE 2421
%rax Return value %r8 Caller Saved
5th parameter Caller Saved
%rbx Callee Saved %r9 %rcx 4th parameter %r10
6th parameter Caller Saved
Caller Saved
Caller Saved Caller Saved Callee Saved Callee Saved Callee Saved Callee Saved
%rdx 3rd parameter %r11 Caller Saved
%rsi 2nd parameter %r12 Caller Saved
%rdi 1st parameter %r13 Caller Saved
%rsp Stack Pointer %r14 %rbp Callee Saved %r15
J. E. Jones
OSU CSE 2421
It depends!
◦ On what parameters our function is passed
◦ On what registers our function wants to use
◦ On what other functions our function might call ◦ How we can minimize save/restore activity
Efficiency is why we got here in the first place, remember?
J. E. Jones
OSU CSE 2421
Must save and restore any of these registers a function plans to use:
◦ %rbp: used as part of the stack frame and planned to restore it anyway
◦ %rsp: if we don’t restore it, the program will probably crash. Assume all functions deal with %rsp correctly.
◦ %rbx and %r12-%r15: Must save these before we use them
J. E. Jones
OSU CSE 2421
Leaf functions make no calls to any other function
Can freely use %rdi, %rsi, %rdx, %rcx, %r8 and
%r9 even when passed fewer than 6 parameters.
Can freely use %r10 and %r11 since these are caller
saved registers
Can freely use %rax as long as function fills it with the return value prior to returning to the caller.
J. E. Jones
OSU CSE 2421
Trade-offs to be made:
◦ If we make many calls to other functions, we must save and restore any of the parameter registers as well as %r10/%r11 before and after each call if we still want to use the values they contained prior to the call.
◦ If we use %rbx, %rbp, %r12-%r15 for our function’s work, we only save them one time (at the beginning) and restore them one time (at the end) of our function.
J. E. Jones
OSU CSE 2421
Because the stack frame for the procedure is set up at the beginning of its code, a procedure which calls itself recursively will get a new stack frame each time it is called.
The stack frame for the second call of the procedure will be above the stack frame for the first (i.e., higher in the stack, but at a lower-numbered address), and so on.
When each call returns, the frame pointer of the previous call will be restored, and at that point, what is at the top of the stack will be the return address from the previous call.
Therefore, when the ret instruction is executed at the end of the recursive function’s assembly code, execution will return to the point in the code of the function from which the call was made.
So, just how deep of a recursive procedure do you want to have in your code given all the resources each call is going to use? Hmmm?
J. E. Jones
OSU CSE 2421
We can call any C-Library function that we used in our C-language programs from any x86-64 program that we write
◦ Seems kinda odd to call a C function from an assembler one doesn’t it?
Parameters must be passed using the caller/callee/return value paradigm described previously
Assume that all caller saved registers will be trashed after return and plan accordingly
◦ Push values you want to keep to the stack
J. E. Jones
OSU CSE 2421
From section 3.5.7 Variable Argument Lists:
Some otherwise portable C programs depend on the argument passing scheme, implicitly assuming that all arguments are passed on the stack, and arguments appear in increasing order on the stack. Programs that make these assumptions never have been portable, but they have worked on many implementations. However, they do not work on the AMD64 architecture because some arguments are passed in registers. Portable C programs must use the header file in order to handle variable argument lists. When a function taking variable-arguments is called, %rax must be set to the total number of floating-point parameters passed to the function in vector registers.
Since we won’t be passing any floating-point parameters (we’re only using integers in this class), we will always have to set %rax to zero before calling a function that allows a variable argument list.
*the section above references AMD64 architecture, but x86-64 is equivalent
J. E. Jones
OSU CSE 2421
What functions did we use in C that had variable argument lists?
◦ printf() family ◦ scanf() family
You have to make a point to set %rax to zero prior to calling printf() or scanf(), because if you do not, expect your program to seg fault.
No only that, but fully expect the information in all “caller saved registers” to be totally trashed upon return
J. E. Jones
OSU CSE 2421
Consider the following simple C program, with two functions. It illustrates the X86 conventions for parameter passing, return value, and use of caller and callee save registers.
First, main:
long sum(long count, long *array); int main() {
}
static long array[4] = {10, 12, 15, 19};
long count= 4; /* number of array elements */ long result;
result = sum(count, array);
printf(“The sum of the array is %i\n”, result);
J. E. Jones
OSU CSE 2421
Now, sum():
long sum(long count, long *array) {
long result = 0;
long i;
for (i = 0; i < count; i++) {
result = result + array[i]; }
return(result); }
J. E. Jones
OSU CSE 2421
The next slide shows X86 assembler directives to set up space in memory for:
1. the static array,
2. output, and
3. defining main() as a function
All of the following code is in a single file called sumprog.s.
J. E. Jones
OSU CSE 2421
.file “sumprog.s”
# Assembler directives to allocate storage for static array .section
.rodata
printf_line:
.string “The sum of the array is %i\n”
.data
.align 8
array:
.quad 10 .quad 12 .quad 15 .quad 19
.globl main
.type main, @function
# guarantee that we are starting on an 8-byte boundary # this is a LABEL
J. E. Jones
OSU CSE 2421
.text main:
pushq %rbp
movq %rsp, %rbp
# save caller’s %rbp
# copy %rsp to %rbp so our stack frame is ready to use
movq $array, %rsi movq $4, %rdi
# set %rsi (2nd parameter) to point to start of array # set %rdi (1st parameter) to count = 4
# (i.e. caller saved registers)
# since we aren’t using %rsi or %rdi values or the # value in any other caller saved registers,
call sum
movq %rax, %rsi
movq $printf_line, %rdi
# Write return value to 2nd parameter # Write string literal to 1st parameter
movq $0, %rax call printf leave
ret
# Need 0 in %rax for System V ABI requirement
.size main, .-main
# we don’t have to push them
J. E. Jones
OSU CSE 2421
.globl sum
.type sum, @function
sum:
pushq %rbp movq %rsp, %rbp
#save caller’s rbp
#set function’s frame pointer
# register %rdi contains count (1st parameter)
# register %rsi contains address to array (2nd parameter) # initialize sum to 0, by putting 0 in %rax,
# it’s where return value
# needs to be when we return
# loop to sum values in array
# decrement number of remaining elements by 1
# jump out of loop if no elements remaining
# add element to sum
# jump to top of loop
# sum already in register %rax so ready to return
movq $0, %rax
loop: decq
%rdi
jl exit
addq (%rsi,%rdi,8), %rax jmp loop
exit:
leave
ret
.size sum, .-sum
#return to caller’s code at return address
J. E. Jones
OSU CSE 2421
The following slides show what would change if sum() wasn’t a “leaf function”.
Let’s see how the code would change if we called printf()...
J. E. Jones
OSU CSE 2421
.file “sumprog.s”
# Assembler directives to allocate storage for static array .section
.rodata
printf_line:
.string “The sum of the array is %i\n”
printf_literal1:
.string “We are in the sum() function.\n”
.data
.align 8
array:
.quad 10 .quad 12 .quad 15 .quad 19
.globl main
.type main, @function
# guarantee that we are starting on an 8-byte boundary # this is a LABEL
J. E. Jones
OSU CSE 2421
. sum:
pushq %rbp movq %rsp, %rbp
#save caller’s rbp
#set function’s frame pointer
# register %rdi contains count (1st parameter)
# register %rsi contains address to array (2nd parameter)
# initialize sum to 0, by putting 0 in %rax,
# it’s where return value
# needs to be when we return
# %rax, %rdi, %rsi have values in them that our program needs
movq $0, %rax
pushq %rax
pushq %rdi
pushq %rsi
movq $printf_literal1, %rdi movq $0, %rax
# some literal string with no % entries, so no other parameters # no Mr. Resetti today!
call printf popq %rsi popq %rdi popq %rax
# get values back so my loop still works
# Note that the pop instructions are in the opposite order of the pushes
loop: decq
%rdi
# loop to sum values in array
# decrement number of remaining elements by 1 # jump out of loop if no elements remaining
# add element to sum
# jump to top of loop
# sum already in register %rax so ready to return
jl exit
addq (%rsi,%rdi,8), %rax jmp loop
exit:
leave
ret
.size sum, .-sum
#return to caller’s code at return address
J. E. Jones