Procedure calls and stack frames in hardware
How the hardware supports procedure calls: the stack pointer, push and pop, CALL and RET, the frame layout (arguments, return address, saved frame pointer, locals), prologue and epilogue, register saving, recursion depth and stack usage.
Why it exists
A procedure can be called from many places, can call other procedures and, with recursion, can call itself. So the hardware needs three things at the moment of a call: where to come back to (the return address), where the arguments are, and fresh storage for the procedure's own local variables, which must not overwrite the storage of an earlier, still-active call. A last-in first-out stack in memory solves all three: the most recent call is always the first one to finish. The hardware support is small, a stack pointer register and a CALL/RET pair, and the rest is convention. GATE asks for the contents of the stack after a sequence of calls, the value of the stack pointer, the memory a recursion needs, and what happens to the return address.
Prerequisites
You need register-indirect and displacement addressing from addressing modes, and the idea of the call stack from recursion and the call stack.
Precise definitions
The stack. A region of memory used last-in first-out, managed with a stack pointer (SP). Push stores a word and moves SP; pop reads a word and moves SP back. Two choices matter in every question:
- Direction of growth. Most machines grow the stack toward lower addresses ("descending"). Then a push decreases SP and a pop increases it.
- What SP points at. Either at the last word pushed (the top, "full") or at the next free word ("empty"). With a full descending stack: push is and pop is . With an empty descending stack the order of the two steps is swapped.
(Units: SP changes by the size of a word, 1 on a word-addressed machine, 4 on a byte-addressed machine with 4-byte words.)
CALL and RET. CALL target pushes the return address (the PC after the fetch of the CALL, i.e. the address of the next instruction) and sets . RET pops the return address into the PC. Some ISAs (RISC) instead save the return address in a link register and have the callee push it only if it makes further calls.
Stack frame (activation record). The stack area belonging to one active call. A typical layout from higher to lower addresses: the arguments (pushed by the caller), the return address (pushed by CALL), the saved frame pointer (pushed by the callee), the callee's saved registers, and its local variables and temporaries. The frame pointer (FP) is a register set to a fixed point inside the frame, so that arguments and locals have fixed offsets from FP (positive for the arguments, negative for the locals) even while SP moves as temporaries are pushed.
Prologue and epilogue. On entry (prologue): push the old FP, copy SP to FP, and subtract the size of the locals from SP. On exit (epilogue): copy FP to SP (discarding the locals), pop the saved FP, then RET.
Register saving. A caller-saved register may be changed by the callee, so the caller must save it before the call if it needs the value afterwards; a callee-saved register must be restored by the callee before returning, so the callee pushes it on entry and pops it on exit. The split is a convention of the platform.
Parameter passing. Arguments are passed in registers (fast, only a few) or on the stack (any number). The result is returned in a register. Who removes the stack arguments (the caller or the callee) is also a convention.
Recursion and stack usage. Each active call has its own frame, so a recursion of depth with frames of words uses words of stack. The depth counts simultaneously active calls, not the total number of calls: a function that makes 177 calls in all (such as fib(10)) needs only 10 frames at once. Exceeding the stack's size is a stack overflow.
Interrupts use the same mechanism: the hardware pushes the PC (and flags) before running the handler (interrupts).
How it works
The animation executes a call to f(1, 2) where f(a, b) returns . The stack grows downward from address 100; the memory rows are the stack words. Follow SP and FP: the caller pushes the arguments, CALL pushes the return address, the prologue saves FP and allocates a local, the body reads the arguments at FP + 2 and FP + 3 and writes the local at FP − 1, and the epilogue and RET undo everything in reverse order.
A call f(1, 2) on a downward-growing stack: step 1
Machine run, not typed(program running in main)| addr | decimal | meaning | |
|---|---|---|---|
| 064 | . | MAR | |
| 063 | . | arg b = 2 | |
| 062 | . | arg a = 1 | |
| 061 | . | return address | |
| 060 | . | saved FP | |
| 05F | . | local sum | |
| 05E | . |
Step 1
Stack memory is empty. SP and FP both hold 100: the stack grows toward LOWER addresses, and SP points at the last word pushed (here nothing yet). main is about to call f(1, 2), where f(a, b) returns a + b through R0. The CALL instruction is at address 12.
After the call, SP, FP and PC are as they were (PC has advanced to the return address 13), but the old words (arguments, return address, saved FP, local) are still in memory. They are above SP, so they are dead and will be overwritten by the next call.
Worked examples
EX 1Recursion depth in a program
Easy#include <stdio.h>
static int depth, max_depth, calls;
static long fact(int n) {
depth++;
calls++;
if (depth > max_depth) max_depth = depth;
long r = n <= 1 ? 1 : n * fact(n - 1);
depth--;
return r;
}
static int fib(int n) {
depth++;
calls++;
if (depth > max_depth) max_depth = depth;
int r = n < 2 ? n : fib(n - 1) + fib(n - 2);
depth--;
return r;
}
int main(void) {
printf("fact(5) = %ld: %d calls, deepest nesting %d frames\n", fact(5), calls, max_depth);
calls = max_depth = 0;
printf("fib(5) = %d: %d calls, deepest nesting %d frames\n", fib(5), calls, max_depth);
calls = max_depth = 0;
printf("fib(10) = %d: %d calls, deepest nesting %d frames\n", fib(10), calls, max_depth);
/* stack use = frames x frame size: with 16-byte frames */
printf("fact(100) needs %d frames = %d bytes of stack at 16 bytes per frame\n", 100, 100 * 16);
printf("an 8 MB stack holds about %d such frames\n", 8 * 1024 * 1024 / 16);
return 0;
}fact(5) = 120: 5 calls, deepest nesting 5 frames
fib(5) = 5: 15 calls, deepest nesting 5 frames
fib(10) = 55: 177 calls, deepest nesting 10 frames
fact(100) needs 100 frames = 1600 bytes of stack at 16 bytes per frame
an 8 MB stack holds about 524288 such framesReading it. fact(5) makes 5 calls and the nesting reaches 5 frames. fib(5) makes 15 calls in total but never has more than 5 active at once, and fib(10) makes 177 calls with a maximum depth of 10. Stack space depends on the depth, not on the call count. With 16-byte frames a recursion 100 deep uses 1600 bytes, and an 8 MB stack could hold about half a million such frames.
EX 2Stack pointer arithmetic
EasyA byte-addressed machine with 4-byte words has SP = 1000 before the call and the stack grows downward (SP points at the last word pushed). The caller pushes 2 arguments: SP = 996, then 992. CALL pushes the return address: 988. The prologue pushes the old FP: 984, and sets FP = 984. It allocates 3 locals (12 bytes): SP = 972. The whole call (2 arguments, return address, saved FP and locals) therefore uses bytes, of which the callee's own frame is . The epilogue sets SP = FP = 984, pops the FP (988), RET pops the return address (992), and the caller removes the two arguments (1000). The script recomputes every value.
EX 3Which address holds what?
MediumIn the animation, FP = 96. The saved FP is at FP itself (96), the return address at FP + 1 (97), the first argument at FP + 2 (98), the second argument at FP + 3 (99), and the local variable at FP − 1 (95). The arguments were pushed right to left: first (higher address), then , so that the first argument is closest to the return address. A displacement addressing mode 2(FP) reaches and -1(FP) reaches the local.
EX 4Push and pop sequences
MediumA descending stack with SP pointing at the top word, SP = 100. PUSH A, PUSH B: SP = 98 with and . POP returns B and SP = 99. PUSH C stores C at 98 (overwriting B's old position) and SP = 98. Two more pops return C and then A and SP = 100 again. A question that gives the opposite convention (SP points at the next free word) shifts every address by one.
EX 5Total stack for nested calls
GATE levelmain calls f, which calls g. f has locals and saved registers using 24 bytes plus a 4-byte return address; g has 40 bytes plus a 4-byte return address. At the deepest point (inside g) the stack holds bytes above main's frame. If g is recursive with depth 100, the stack use grows by 44 bytes per level, to bytes. Frame sizes of the callers are added for every call still active; the frame of a function that has returned is gone.
EX 6Link register machines
GATE levelOn a machine whose call instruction stores the return address in a link register LR instead of on the stack, a leaf function (one that calls nothing) can return with JUMP LR and needs no memory access for the return address. A function that makes another call must first push LR (otherwise the inner call overwrites it) and pop it before returning. So a program with deep nesting pushes return addresses on the stack just the same, but leaf calls are cheaper.
Cheat box
Cheat box
- Descending, full stack: push ; pop . Step = word size.
- CALL: push the return address (PC after the fetch), target. RET: pop into PC.
- Frame (high to low): arguments, return address, saved FP, saved registers, locals. FP + positive offsets reach arguments, FP + negative offsets reach locals.
- Prologue: push FP; ; . Epilogue: ; pop FP; RET.
- Recursion depth , frame words: stack . Depth = active calls, not total calls.
- Callee-saved registers are pushed by the callee; caller-saved by the caller if needed later.
- Dead frames stay in memory but above SP.
Traps and GATE patterns
Connections
Across subjects
The software view of the same mechanism, with a C program's call stack, is in recursion and the call stack and parameter passing. The frame-relative access to arguments and locals is displacement addressing. An interrupt pushes the PC just as CALL does. CALL and RET are ordinary instructions of the ISA executed in the instruction cycle: CALL's execute phase pushes, then overwrites the PC.
PYQs
2 previous year questions from your book. I re-solved each one myself (2 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider a processor with byte-addressable memory. Assume that all registers, including Program Counter (PC) and Program Status Word (PSW), are of size 2 bytes. A stack in the main memory is implemented from memory location (0100)16 and it grows upward The stack pointer (SP) points to the top element of the stack. The current value of SP is (016E)16. The CALL instruction is of two words, the first word is the op-code and the second word is the starting address of the subroutine (one word = 2 bytes). The CALL instruction is implemented as follows: • Store the current value of the PC in the stack. • Store the value of the PSW register in the stack. • Load the starting address of the subroutine in PC The content of the PC just before the fetch of a CALL instruction is (5FA0)16. After execution of the CALL instruction, the value of the stack pointer is
- (a)(O16A) 16
- (b)(O16C) 16
- (c)(O170) 16
- (d)(O172) 16 Instr. No. Instruction i: add R2, R3, R4 i + 1: sub R5, R6, R7 i + 2: cmpR1, R9, R10 i + 3: beqR1, Offset Computer Organization and Architecture GATE MCQ – 2008 MCQ (1-MARKS) GATE MCQ – 2008 MCQ (2-MARKS)
Assume that EA = (X)+ is the effective address equal to the contents of location X, with X incremented by one word length after the effective address is calculated; EA = -(X) is the effective address equal to the contents of location X, with X decremented by one word length before the effective address is calculated; EA = (X)- is the effective address equal to the contents of location X, with X decremented by one word length after the effective address is calculated. The format of the instruction is (opcode, source, destination), which means (destination ← source op destination). Using X as a stack pointer, which of the following instructions can pop the top two elements from the stack, perform the addition operation and push the result back to the stack. GATE MCQ – 2008 MCQ (1-MARKS) GATE MCQ – 2011 MCQ (1-MARKS) Computer Organization and Architecture
- (a)ADD (X)-, (X)
- (b)ADD (X), (X)-
- (c)ADD -(X), (X)+
- (d)ADD -(X), (X)
Self-check
Question 1
A descending stack (SP points at the last word pushed) of 4-byte words starts with SP = 5000. After 3 pushes and 1 pop, what is SP?
Show answerHide
.
Question 2
fact(n) has a 24-byte frame per call. How many bytes of stack does fact(50) need at its deepest point?
Show answerHide
50 active frames at the deepest point: bytes.
Question 3
What value does CALL push if it is located at address 600 and is 4 bytes long on a byte-addressed machine?
Show answerHide
The return address 604 (the address of the next instruction).
Question 4
Why are arguments and locals addressed relative to the frame pointer rather than to the stack pointer?
Show answerHide
SP moves whenever a temporary is pushed or popped, so offsets from SP change during the function. FP is fixed for the whole call, so each argument and local has one constant offset.
Revision card
One-screen revision
- Descending full stack: push then store; pop reads then .
- CALL pushes the return address (next instruction); RET pops it into PC.
- Frame: args, return address, saved FP, saved registers, locals; FP gives fixed offsets.
- Stack use = (active depth) (frame size). Depth is not the call count.
- Callee-saved registers are saved by the callee. Old frames stay in memory, above SP.
References
- ·Hennessy and Patterson, Computer Organization and Design, Chapter 2 Instructions: Language of the Computer (supporting procedures in computer hardware)
- ·Bryant and O'Hallaron, Computer Systems: A Programmer's Perspective, 3rd ed., Chapter 3 Machine-Level Representation of Programs (procedures)
- ·Hamacher, Vranesic, Zaky, Computer Organization, 5th ed., Chapter 2 Machine Instructions and Programs (subroutines, the stack frame)
Verification
The call animation is a simulator run (scripts/gen-coa-traces.py) in which SP, FP, PC and R0 and every memory word follow the stated micro-operations; tests/coa-traces.test.ts checks that SP, FP and PC are restored, that the result is a + b and that the frame has the layout stated here. Recursion depth and call counts are checked by an independent recursion in scripts/enum-checks/coa_isa.py and by the C program (npm run verify:c); the stack-pointer arithmetic of the examples is also computed there. The layout used (stack grows toward lower addresses, SP points at the last word pushed, arguments pushed right to left, caller removes arguments) is one common convention, not the only one; the text says where others differ. Real frame sizes depend on the compiler and the architecture. Textbook chapter titles are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). One loose sentence fixed (the 28 bytes of the stack-pointer example are the whole call, not the callee's own frame). Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)