Assembly Language & Procedure Calls

caller-saved versus callee-saved registers

There are only so many registers, and both a function and the functions it calls want to use them. Without an agreement they would constantly clobber each other's values. The solution is to split the registers into two groups by who is responsible for preserving them across a call. Picture two roommates sharing a kitchen: some shelves are yours to leave messy (whoever uses them cleans up before they matter), and some are shared and must always be tidy.

Caller-saved registers (also called volatile or temporary) are fair game for a called function to overwrite. If the caller has a value in such a register that it needs after the call, it is the caller's job to save it (to the stack) before calling and restore it after. Callee-saved registers (non-volatile, saved) carry the opposite promise: a called function may use them, but only if it first saves the old contents and restores them before returning, so the caller never notices. On RISC-V the t-registers (t0 through t6) are caller-saved and the s-registers (s0 through s11) are callee-saved.

The split matters because it minimizes wasted saving. If every value were always saved, calls would be slow; if nothing were ever saved, values would vanish. Splitting lets a compiler put short-lived values in caller-saved registers (cheap because they often need no saving) and values that must survive across many calls in callee-saved registers (saved once at function entry). The honest pitfall when writing assembly by hand: forget which group a register is in, and you will either lose a value you needed or fail to restore one you promised to, producing a bug that only shows up across a call boundary.

Need a value to survive a call? Put it in a callee-saved register and save that register once: myfunc: addi sp, sp, -16 sd ra, 8(sp) sd s0, 0(sp) # save s0 because I'm about to use it across a call mv s0, a0 # keep the argument safely in s0 jal ra, helper # helper may trash t0-t6, but must preserve s0 add a0, a0, s0 # s0 is still my saved value, guaranteed ld s0, 0(sp) ld ra, 8(sp) addi sp, sp, 16 ret

Values needed after a call go in callee-saved registers; short-lived scratch goes in caller-saved temporaries.

The names are about responsibility, not safety: a caller-saved register is one the caller must save if it cares; a callee-saved register is one the callee must save if it uses it. Mixing them up is a classic ABI bug.

Also called
volatile vs non-volatile registers暫時暫存器與保存暫存器