The protocol in four lines
A is a fixed exchange between two functions, in a fixed order:
- The caller puts the arguments in
$a0,$a1,$a2,$a3. jal fwrites$ra= its own address + 8 and jumps tof.fleaves its result in$v0(a 64-bit result uses$v1too).jr $rajumps back to the address in$ra.
The call protocol: arguments in, jal, result out, jr $ra
Four crossings between caller and callee, always in this order and always through these registers.
What to notice
Nothing else crosses between the two boxes. The caller does not know how
square works; square does not know who called it. They agree only on
the four registers and the order, and that agreement is the o32 convention
of §2.2.
Watch a call
The runner calls square twice with different arguments. It pauses before
the first jal to ask you which registers will change.
Calling a leaf function twice: $a0 in, jal, $v0 out, jr $ra back
How does one function call another and get an answer back?
program (address · word · instruction)
| PC → | 0xbfc00000 | 0x3c191000 | _start:lui $t9,0x1000 |
| 0xbfc00004 | 0x37390ff0 | ori $t9,$t9,0xff0 | |
| 0xbfc00008 | 0x24040006 | li $a0,6 | |
| 0xbfc0000c | 0x0ff0000d | jal 0xbfc00034 | |
| 0xbfc00010 | 0x00000000 | nop | |
| 0xbfc00014 | 0xaf220004 | sw $v0,4($t9) | |
| 0xbfc00018 | 0x24180020 | li $t8,32 | |
| 0xbfc0001c | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00020 | 0x24040007 | li $a0,7 | |
| 0xbfc00024 | 0x0ff0000d | jal 0xbfc00034 | |
| 0xbfc00028 | 0x00000000 | nop | |
| 0xbfc0002c | 0xaf220004 | sw $v0,4($t9) | |
| 0xbfc00030 | 0xaf200008 | sw $zero,8($t9) | |
| 0xbfc00034 | 0x70841002 | square:mul $v0,$a0,$a0 | |
| 0xbfc00038 | 0x03e00008 | jr $ra | |
| 0xbfc0003c | 0x00000000 | nop |
registers — read · written this tick
console (what the doors printed)
▮tick 0 of 19
Nothing has run yet. The PC (program counter) points at the first instruction.
Before each jal, $a0 turns green with the argument. The jal writes $ra. square reads $a0 and writes $v0. jr $ra brings the PC back to the instruction after the jal's slot, and the caller prints $v0. square touches no stack: it is a leaf.
What to notice
$a0 turns green before each jal, $ra turns green at the jal, $v0
turns green inside square, and the PC arrow comes back to the instruction
after the nop. The two returns land in two different places, because $ra
was different each time.
Leaves and non-leaves
square is a
:
it calls nobody. Its $ra is never overwritten, so it returns with jr $ra
and touches no memory. print_char and print_int in hello.c are leaves
too. At -O0 GCC still gives them an 8-byte frame for $fp, but nothing is
saved except $fp itself.
A
calls others. Its own jal writes $ra, destroying the caller’s return
address. So it must save $ra before the call and restore it after. Here is
outchar from fib.c, which calls print_char:
outchar: a non-leaf that must save $ra (fib.c and the -O0 dump)
carolyne_ref/programs/fib.c; fib_o0.dump bfc0014c–bfc0018c
1int outchar(int a) {2 print_char(a);3 return 0;4}bfc0014caddiu sp,sp,-24bfc00150sw ra,20(sp)bfc00154sw s8,16(sp)bfc00158move s8,spbfc0015csw a0,24(s8)bfc00160lw v0,24(s8)bfc00164seb v0,v0bfc00168move a0,v0bfc0016cjal bfc000b0 <print_char>bfc00170nopbfc00174move v0,zerobfc00178move sp,s8bfc0017clw ra,20(sp)bfc00180lw s8,16(sp)bfc00184addiu sp,sp,24bfc00188jr rabfc0018cnopClick any highlighted line to see it light up in the other listing, and read what it does.
What to notice
sw ra,20(sp) near the top and lw ra,20(sp) near the bottom are the
whole reason this function has a frame. Between them, jal print_char
overwrites $ra; without the save, outchar’s own jr ra would jump back
into outchar forever.
The instructions before the body are the
;
the mirror image at the end is the
.
You met both in §4.4; now you know why the $ra line is there.
Before reading on, decide:
outchar's `jal print_char` sits at 0xBFC0016C. Inside print_char, what is $ra?
Calling with a computed value
The pattern “compute, move into $a0, call” is everywhere. In fib.c’s
DISPLAY_INT, a digit is computed into v0 and passed on:
bfc00230: move a0,v0 # the argument
bfc00234: jal bfc0014c <outchar>
bfc00238: nop
More than four arguments? The fifth and later go on the stack, in the
caller’s frame at 16($sp) and up. That is just above the 16-byte argument
area of §4.4.
None of the reference programs needs that, so this book only mentions it.
Go deeper: why the link goes into a register, and how Carolyne writes it
Many older ISAs pushed the return address onto the stack as part of the call
instruction. MIPS refused: a call that writes memory would be a store hidden
inside a jump, breaking the one-thing-per-instruction rule that keeps the
pipeline simple. So jal writes a register, and software decides whether
that value needs to reach memory. A leaf never pays for a store it does not
need; a non-leaf pays one sw and one lw. This is the same reasoning that
gave MIPS no push and no pop.
Carolyne’s branch unit writes the link the same way it writes any register:
the jal/jalr/bltzal/bgezal µops carry $31 (or rd) as their
one destination, and the body writes pc + 8 through the unit’s writeback
port inside a guard on the linking µops, so a plain j or jr writes
nothing. The reorder buffer then retires it like an ALU result. Nothing in
the engine knows that this particular register is “the return address”.
Can you now explain it?
A call is four crossings: arguments in $a0–$a3, jal writing $ra, the result in $v0, jr $ra back. A leaf needs no frame; a non-leaf saves $ra in its prologue and restores it in its epilogue.
If you remember only three things
- Four registers, one order. $a0–$a3 in, $ra by jal, $v0 out, $ra by jr. Caller and callee agree on nothing else.
- Leaf or not decides the frame. square and print_char keep $ra and need nothing. outchar and main call others, so sw ra and lw ra bracket their bodies.
- The fifth argument walks. Arguments five and up go on the stack above the 16-byte argument area; the first four ride in registers.
Now try to explain this one yourself: in the runner, the caller reloads
$a0 before the second jal square. Could it have relied on $a0 still
being 6 after the first call? Use §2.2’s table.
Go deeper: a model answer, once you have tried
No. $a0 is caller-saved: the callee may overwrite it, and the caller must
assume it did. square happens not to touch $a0, but a different square
that computed with $a0 in place would be equally correct under the
convention. A value the caller needs after a call belongs in an $s register
(saved by the callee, §6.2) or on the caller’s own stack. The runner’s
caller does the simple thing: it reloads $a0 before every call.
Check your understanding
0/5 answered1.Put the four steps of a call in order.
2.In the runner, where does square find its argument, and where does it leave its result?
3.Why does square need no stack frame at all?
4.A C function takes five int arguments. Where does the fifth go?
5.print_char and print_int are leaves, yet at -O0 each has an 8-byte frame. What is in it, and why does it vanish at -O2?