A subroutine (function, procedure) is a block of code that can be invoked from multiple places, executed, and returned from to the calling site. The point is reuse — write the code once, call it from anywhere.

Subroutines need two extra mechanisms beyond plain instructions:

  1. A way to remember where to return when the subroutine finishes — so the program continues from the instruction after the call.
  2. A way to pass arguments and receive results — the subroutine needs input, and gives output.

The processor provides hardware support for #1 via the call and ret instructions; #2 is by convention, using registers and/or the stack.

The Call instruction

A call is a special branch that:

  1. Saves the address of the next instruction (PC + 4 in a 32-bit ISA) — the return address.
  2. Branches to the subroutine’s entry point.

Where it saves the return address depends on the ISA:

  • Link register style (Nios II, MIPS, ARM): saves to a dedicated link register (r31 / ra in Nios II).
  • Stack style (x86): pushes onto the stack.

In RTN (see Register transfer notation):

(For link-register style. Whether the PC has already been incremented to PC + 4 in the Fetch stage of the call instruction depends on the pipeline organization, so different texts write this either as LR ← [PC] with a comment that PC is already PC + 4, or as LR ← [PC] + 4 to make the increment explicit. The latter form is unambiguous.)

The Return instruction

A ret jumps to the address held in the link register (or popped from the stack):

That returns control to the instruction immediately after the original call.

In Nios II

call MySub      # jump to MySub, save (PC+4) into r31
...             # MySub returns here
MySub:
    ...         # subroutine body
    ret         # jump to address in r31

ret is a special form of indirect branch: it always reads from r31. (You could equivalently write jmp r31.)

Calling a subroutine — overview of mechanics

Walking through call CountGTorEQAvg:

  1. Caller sets up arguments: e.g., loads list, n, avg into r4, r5, r6 (per the Nios II ABI, which uses r4–r7 for the first four arguments).
  2. Caller executes call CountGTorEQAvg. Hardware saves PC + 4 into r31, jumps to CountGTorEQAvg.
  3. Callee (CountGTorEQAvg) saves any registers it plans to clobber (besides the argument and return registers) onto the stack.
  4. Callee does its work, accumulating the result in some register.
  5. Callee restores the saved registers from the stack.
  6. Callee puts the return value in a designated register (r2 by convention in Nios II).
  7. Callee executes ret, jumping back to the saved address in r31.
  8. Caller picks up the return value from r2 and continues.

Argument and return-value conventions

The choice of which registers to use for arguments and return values is the calling convention. In Nios II:

  • Arguments: r4, r5, r6, r7 (first four in registers; rest on the stack).
  • Return value: r2 (and r3 for 64-bit return values).
  • Caller-saved: r2..r15 — caller must save before calling if it cares.
  • Callee-saved: r16..r23 — callee must preserve if it uses them.
  • Special / reserved: r24..r31 hold dedicated values (et, bt, gp, sp, fp, ea, ba, ra) — neither freely caller- nor callee-saved.

Conventions vary by ISA but the structure is universal: some registers carry arguments, some hold returns, some are guaranteed preserved.

Nesting

When a subroutine calls another subroutine (Subroutine nesting), the new call overwrites r31. Without intervention, the original return address is lost.

The fix: each subroutine that does its own calls must save r31 to the stack on entry and restore it before returning. With this discipline, calls can nest arbitrarily deep, limited only by stack size.

For the full call/return discipline including argument and register preservation, see Subroutine linkage.