1Learning Outcomes¶
Explain why register calling convention helps implement procedure calls in RISC-V.
Identify whether the caller or callee is responsible for each of the six fundamental steps to procedure calls.
Use the RISC-V green card to determine whether registers are caller-saved, callee-saved, or neither.
🎥 Lecture Video
We have previously discussed register names and register conventions:
Register names define convention—that is, specifying how assembly instructions should use specific registers for specific common functions. These restrictions help build “agreement” upon how to translate separate components of a program so that the assembly instructions slot together.
Consider the register convention table on the RISC-V green card. So far we have only discussed a few register conventions–namely, the stack pointer sp and the return address ra.
The remainder of the register conventions we will discuss in this course[1] pertain to how to use registers between procedure calls.
2Motivation¶
In the C code below, main calls sum_square, which makes two calls to mult.
1 2 3 4 5 6 7 8 9 10 11 12int main() { int z = sum_quare(3, 4); ... } int mult(int x, int y) { return x * y; } int sum_square(int x, int y) { return mult(x, x) + mult(y, y); }
As we discussed in a previous section, jal ra Label and jr ra are a common instruction pair that saves a return address into register ra. Register naming conventions mean that in both instructions, ra refers to the same register number x1. However, necessarily sum_square will want to jump back to some ra, but this will be overwritten by both calls to mult. We therefore need to save sum_square’s return address somewhere before the call to mult—let’s use the stack!
Show Explanation
Using the fundamental steps of procedure calls discussed in an earlier section, we share what happens from the caller factorial(2)'s perspective.
factorial(2)Prepare for calleefactorial(1)by putting the argument1to registera0.Transfer control to callee
factorial(1)by executing ajal ra factorialinstruction.(omitted;
factorial(1)'s task)(omitted;
factorial(1)'s task)(omitted;
factorial(1)'s task)(omitted;
factorial(1)'s task)
At this point, factorial(2) has regained control and expects that a0 has the return value from factorial(1).
Next, factorial(2) wants to multiply this return value by its own argument, 2. Unfortunately, at this point, register a0 has been overwritten!
3Register Calling Convention¶
Consider the fundamental steps of function calls. As part of Step 2 (where a caller transfers control and execution to a callee), how might a caller “save” their curent registers?
Instead, RISC-V defines a calling convention:
A set of generally accepted rules as to which registers will be unchanged after a procedure call, and which registers may be changed.
In other words, to minimize expensive loads and stores between procedure calls, RISC-V calling convention divides registers into two categories:
Volatile, temporary registers that are not preserved across a procedure call.
Saved registers that are preserved across the procedure call.
There are multiple ways of specifying this convention. The ASM Manual specifies the convention as whether registers are preserved across a procedure call (“yes” or “no”). The convention in Table 1 specifies who must save the register values (“caller” or “callee”).
Caller-saved volatile registers. The caller is responsible for saving these register values for itself before calling a callee. After the callee returns, the caller can then restore these values if needed.
Callee-saved saved registers. The callee is responsible for saving and restoring these register values. These two steps are often performed in the prologue and epilogue, respectively.
Table 1:RV32I Register Calling Convention. This table is also available on our course green card.
| Register(s) | Name | Description | Saver |
|---|---|---|---|
x0 | zero | Constant 0 | - |
x1 | ra | Return Address | Caller |
x2 | sp | Stack Pointer | Callee |
x3 | gp | Global Pointer[1] | - |
x4 | tp | Thread Pointer[1] | - |
x5-7 | t0-2 | Temporary Registers | Caller |
x8 | s0 / fp | Saved Register 0 / Frame Pointer | Callee |
x9 | s1 | Saved Register | Callee |
x10-11 | a0-1 | Procedure Arguments / Return Values | Caller |
x12-17 | a2-7 | Procedure Arguments | Caller |
x18-x27 | s2-11 | Saved Registers | Callee |
x28-31 | t3-6 | Temporaries | Caller |
4Fundamental Steps, Revisted¶
In light of calling convention, we revisit the Six Fundamental Steps to Procedure Calls from a previous section in more detail: