Base CS from zero
What a function is
You have traced programs where every instruction runs once, top to bottom. You have seen loops repeat a block by jumping backward. But there is a third kind of jump that programs rely on constantly: the jump to a named block of instructions somewhere else in memory, with the promise of coming back when that block finishes.
This jump is called a function call. It is the mechanism behind every subroutine, method, and procedure in every programming language. Before looking at what happens to the stack when a function is called — which is the next lesson — you need to understand what a function is at the machine level: a labelled address in memory, a jump target, and a return point.
After this lesson you can define a function as a named block of instructions, explain what “calling” a function means in terms of a CPU jump, explain what “returning” means (jumping back to where the call happened), and state why naming and reuse are the two practical reasons functions exist.
Instructions live at addresses, and a function is a label for one of them. You know from Unit 02 that memory is a row of numbered cells. Instructions are just data sitting in those cells. The CPU reads them one by one, following the program counter. A function is nothing more than a block of consecutive instructions starting at a particular address, and a name assigned to that address.
In machine code, names do not exist at runtime — the CPU only knows addresses. But the
compiler and the assembler translate the name you write in source code into the address
where those instructions live. When you write greet() in TypeScript, the compiler
produces a machine-code CALL instruction that contains the address of the first
instruction of greet. The name is a convenience for humans; the hardware sees an
address.
Calling a function: the CPU jumps to the function’s first instruction. A call is a special jump. Like the conditional and unconditional jumps you saw in Unit 07 on control flow, a call changes the program counter to point to a different address. The difference is that a call also records where it came from — the address of the instruction immediately after the call — so the CPU can find its way back.
Concretely:
- Before the call: the program counter points to the
CALLinstruction. - The
CALLinstruction executes: the program counter is set to the first instruction of the function. The return address (the instruction right after theCALL) is saved. - The function’s instructions execute one by one.
- When the function finishes, a
RETinstruction fires: the program counter is restored to the saved return address. Execution continues exactly where it left off.
Returning: jumping back to the caller. The RET instruction is the function’s exit.
It reads the saved return address and sets the program counter to it. From the CPU’s
perspective, a return is just another jump — but this time the destination was recorded
at call time, not written in source code.
The term caller refers to the code that issued the CALL. The term callee refers
to the function being called. After RET fires, control is back with the caller, which
continues at the instruction right after where it called the callee.
Why this works
Why does the hardware bother recording the return address rather than just using a
regular jump? Because the same function can be called from many different places in the
program. A greet function called on line 10 must return to line 10’s sequel; the same
greet called on line 50 must return to line 50’s sequel. A fixed jump target could
only return to one place. Recording the return address at each call site lets the same
function body serve callers from anywhere in the program.
Why functions exist: naming and reuse. Two concrete benefits follow from giving a block of instructions a name and making it callable:
Naming. A function name turns a sequence of low-level operations into a
human-readable intention. sendEmail(to, subject, body) says what it does without
listing how. The reader understands the intent without reading the implementation. The
code closer to the machine level can stay isolated inside the function body.
Reuse. Without functions, every place in a program that needs the same logic would need a copy of it. Copies drift apart when bugs are fixed in one but not the others. With a function, there is exactly one copy of the instructions at exactly one address in memory. Every caller jumps to that address. Fixing the function fixes all callers at once.
These two benefits — naming and reuse — are the practical reason every programming language, from assembly to TypeScript, has the function concept.
Tracing a call and return by address.
Suppose memory contains:
- Address 20:
CALL 80(call the function whose body starts at address 80) - Address 21:
ADD R0, 1(the instruction that runs after the function returns) - Address 80:
MUL R1, 2(first instruction of the function) - Address 81:
RET(last instruction of the function)
Step 1 — Program counter is at 20. The CPU reads CALL 80. It saves 21 as the
return address (the next instruction after the call), then sets the program counter to 80.
Step 2 — Program counter is at 80. The CPU executes MUL R1, 2. Program counter
advances to 81.
Step 3 — Program counter is at 81. The CPU executes RET. It reads the saved return
address (21) and sets the program counter to 21.
Step 4 — Program counter is at 21. The CPU executes ADD R0, 1. Normal execution
continues. The function’s body at 80–81 is completely finished.
The function body at addresses 80–81 can be called from any address in the program. Each call saves its own return address, so each call returns to the correct place.
Common mistake
A common mental-model error is thinking that a function call is like a copy-paste of the
function body at the call site. It is not. The function body exists once at one address.
The CALL instruction jumps to that address. When the function returns, control comes
back to wherever the caller was. Nothing is duplicated. This distinction matters when
reasoning about recursion later: multiple calls to the same function are multiple jumps
to the same address, each with its own saved return address.
A function body starts at address 200 and ends with RET at address 210. How many addresses does this function occupy?
The CALL instruction is at address 50. The instruction right after the CALL is at address 51. What return address does the CALL save?
The same function is called from address 30 and from address 90. How many copies of the function body exist in memory?
A function is called 5 times during a program run. How many times does its RET instruction execute?
After RET executes, the program counter is set to the return address that was saved at call time. If the CALL was at address 100, what is the address the program counter is set to after RET?
What does the CPU do when it executes a CALL instruction?
A function is a named block of instructions at a fixed address in memory. Calling a
function means executing a CALL instruction that saves the return address (the
instruction right after the call) and then sets the program counter to the function’s
first instruction. Returning means the RET instruction reads the saved return address
and sets the program counter back to it — control returns to the caller exactly where
it left off. The same function body can be called from any number of places because each
call saves its own return address. Functions exist for two reasons: naming (a
function name expresses intent without revealing implementation) and reuse (one copy
of the body serves every caller).