Subroutine linkage is the mechanism a processor uses to handle the call-and-return cycle — saving the return address on call, jumping to the subroutine, and resuming the caller on return. The simplest implementation uses a Link register; more complete schemes also handle argument passing, register preservation, and the stack.
The basic mechanism
The call instruction is a special branch with two effects:
- Save the return address (PC + 4) somewhere safe.
- Branch to the target address.
The ret instruction is a special branch with one effect:
- Branch to the saved return address.
The “somewhere safe” is what differs between linkage methods.
Link register linkage
Used by Nios II, MIPS, ARM, RISC-V. The return address is saved in a dedicated Link register (r31 in Nios II).
Pros:
- Fast — register write is cheaper than a memory store.
- Leaf functions need no further bookkeeping.
Cons:
- Single register holds only one return address. Nested calls overwrite it unless the caller saves it first.
Stack-based linkage
Used by x86. The return address is pushed onto the stack by call, popped by ret.
Pros:
- Nested calls automatic — each push adds a new return address.
Cons:
- Every call costs a memory write.
Putting it all together
A complete linkage scheme covers more than just return addresses. The full discipline (informally, the calling convention) addresses:
- Arguments — how the caller passes values to the callee (registers, stack, or both).
- Return value — which register holds the result.
- Caller-saved registers — registers the caller must save before calling, since the callee is allowed to clobber them freely.
- Callee-saved registers — registers the callee must preserve (save on entry, restore before return) so the caller can rely on them surviving the call.
- Stack frame layout — how locals, saved registers, and parameters are organized within the stack frame.
For Nios II:
| Convention | Registers |
|---|---|
| Argument registers | r4, r5, r6, r7 |
| Return value | r2 (and r3 for 64-bit) |
| Caller-saved | r2..r15 |
| Callee-saved | r16..r23 |
| Special / reserved | r24..r31 (et, bt, gp, sp, fp, ea, ba, ra) |
| Stack pointer | r27 (sp) |
| Frame pointer | r28 (fp) |
| Link register | r31 (ra) |
A subroutine following this convention:
MySub:
# save callee-saved registers we'll use
subi sp, sp, 12
stw ra, 8(sp) # save return address (since we'll call others)
stw r16, 4(sp) # save callee-saved register
stw r17, 0(sp)
# ... do work ...
# restore and return
ldw r17, 0(sp)
ldw r16, 4(sp)
ldw ra, 8(sp)
addi sp, sp, 12
ret
Why the convention matters
Subroutines compiled by different tools or written by different programmers have to inter-operate. If everyone follows the same calling convention, they can — your library function works whether called from C, hand-written assembly, or another library.
Violating the convention (clobbering a callee-saved register without restoring it, putting an argument in the wrong place) results in subtle, hard-to-debug failures: data appears to corrupt mysteriously, returns go to the wrong address, the stack pointer gets misaligned. The convention is invisible when followed and devastating when broken.
For the saving discipline used to enable nested calls without losing return addresses, see Subroutine nesting.