CSSE 132 Mini-reading
The Stack and Procedure Calls in 10 Minutes
Review how stack frames keep function calls separate,
how ARM64 passes values, and why a function sometimes saves x30.
Why This Matters
Every C function needs its own place for local variables and a way to return to the instruction that called it. In CSSE 132, you will use the stack to trace procedure calls, interpret the compiler's ARM64 output, and explain why local variables with the same name in different functions do not interfere with each other. Worksheet 10 applies these ideas to a short function call.
Core Idea
The stack is a region of memory used while a program runs.
When a function begins, it reserves a private region called a stack
frame. The stack pointer, sp, identifies the current top
of the stack. A function can use its frame for local variables and, when
needed, saved register values such as the return address.
One function call
main calls my_func. A frame for
my_func is created above main's frame, so its local
variables are separate from main's locals.
A nested call
If my_func calls do_nothing, another frame is
created. When that call finishes, its frame is released first. This last-in,
first-out behavior is why the structure is called a stack.
In the current ARM64 examples, a C int value uses a 32-bit
wN register. An address or saved return address uses a 64-bit
xN register. The link register is x30; it holds the
address to return to after a bl instruction.
Worked Examples
Example 1: Reserve and release a stack frame
sub sp, sp, 16
str w0, [sp, 12]
...
add sp, sp, 16
ret
sub sp, sp, 16 reserves 16 bytes for this function's frame.
The store writes the 32-bit value in w0 at offset 12 within that
frame. Before returning, add sp, sp, 16 gives the 16 bytes back.
The allocation is 16 bytes even though this particular function stores only a
4-byte int; compilers may allocate extra space for alignment or
other implementation needs.
Example 2: Follow a simple call
int my_func(int x) {
return x + 1;
}
int main() {
int x = 56;
x = my_func(x);
}
mov w0, 56 // caller puts the argument in w0
bl my_func // branch; the next instruction address goes in x30
str w0, [sp, 28] // caller receives the return value from w0
The caller is main, because it calls the
procedure. It puts the first int argument in w0.
bl my_func branches to the callee and replaces x30
with the return address. The callee, my_func,
uses w0 for the input and places its result back in
w0. When it returns, the caller reads 57 from w0.
Example 3: Why a non-leaf function saves x30
Now suppose my_func calls another function:
my_func:
str x30, [sp, -32]! // reserve 32 bytes and save caller's return address
str w0, [sp, 28]
bl do_nothing // overwrites x30 with a new return address
ldr w0, [sp, 28]
add w0, w0, 1
ldr x30, [sp], 32 // restore x30 and release the frame
ret
After main calls my_func, x30 holds
the address in main where execution must resume. But
bl do_nothing needs x30 for a different return
address. Therefore my_func stores its old x30 on the
stack before the nested call and restores it afterward. A function that calls
another function is a non-leaf function; a leaf function makes
no further calls and may not need to save x30.
Example 4: Read the combined instructions
str x30, [sp, -32]!
...
ldr x30, [sp], 32
| Instruction | Equivalent two-step idea |
|---|---|
str x30, [sp, -32]! | Subtract 32 from sp, then store x30 at the new top of the stack. |
ldr x30, [sp], 32 | Load x30 from the current top of the stack, then add 32 to sp. |
The ! in the first instruction means the updated address is
written back to sp before the store. In the second instruction,
the update happens after the load. Together, these instructions make matching
entry and exit steps for the stack frame.
Common Mistakes
Confusing the caller and callee
The caller invokes the function; the callee is the invoked function. Their
responsibilities occur on opposite sides of bl.
Thinking every function saves x30
A leaf function that makes no calls may return using its unchanged
x30. A non-leaf function must preserve its caller's return address
before using bl again.
Mixing up w0 and x30
w0 carries a 32-bit int argument or return value;
x30 carries a 64-bit return address.
Forgetting to restore sp
Every reservation of stack space needs a matching release before the function returns. Otherwise the caller's stack frame is no longer found at the expected location.
Quick Reference
| Concept | Remember |
|---|---|
| Stack frame | A function's private, temporary stack memory. |
sp | Stack pointer; moving it down reserves space and moving it up releases space. |
bl label | Branch to label and save the return address in x30. |
ret | Return to the address held in x30. |
First int argument / return value | Use w0. |
| Non-leaf function | Save and later restore x30 if another call would overwrite it. |
Check Yourself
Try these before opening the answers. The examples differ from Worksheet 10.
- Why can two functions each have a local variable named
countwithout sharing one value? - What does
bl helperdo to execution and tox30? - In a call that passes and returns a C
int, which register is used for the first argument and return value? - Why should a function that calls
helpersavex30before itsbl helperinstruction? - A function executes
sub sp, sp, 16when it begins. What must it do tospbefore returning?
Answers
Show the answers
- Each call has its own stack frame, so each local variable has a different memory location.
- It branches to
helperand stores the address of the following instruction inx30. w0. It holds the first 32-bitintargument before the call and the 32-bit return value afterward.- The nested
bloverwritesx30. Saving the old value lets the function return to its own caller afterhelperfinishes. - Release the same 16 bytes, such as with
add sp, sp, 16.
Next Step
Complete Worksheet 10: Stack / Procedure
Call. As you trace each call, keep a small table of the current function,
sp, w0, and x30.