The Runtime Theory
RuntimeDSAexecution

What happens when you call a function?

A step-by-step walk of a function call: registers, the call instruction, the stack frame, the return address, and what a single call actually costs.

The Runtime Theory Team1 min read05 steps

layer stack

Runtime

HWHardware
KKernel
RTRuntime
APPApplication
SYSSystem
CLIClient
NETNetwork
TLSCrypto
SRVServer

adjacent altitudes in this subsystem are still being traced

trace spine

  1. 01 Arguments land in registers
  2. 02 call pushes the return address
  3. 03 Prologue builds the frame
  4. 04 Body runs against registers and stack
  5. 05 Epilogue tears down, ret returns

Every abstraction you've ever relied on — every method, every closure, every syscall wrapper — bottoms out in the same physical act: a jump to an address with a return address saved on a stack. The call is the most executed instruction sequence in computing, so its mechanics are worth tracing exactly.

trace stepApplication

Before the call executes, the compiler arranges arguments per the calling convention. On x86-64 System V: rdi, rsi, rdx, rcx, r8, r9 for the first six integer/pointer args, the XMM registers for floats; anything beyond goes on the stack. The caller also ensures the stack is 16-byte aligned at the call site. At this point nothing has happened yet except register moves — nanoseconds, no memory traffic.

trace stepHardware

The call instruction does two things atomically: it pushes the return address (the address of the instruction after the call) onto the stack, then jumps to the callee's entry point. The return address push is the entire contract — the callee can always return by popping it. This push is one store to memory; with a hot stack it stays in L1 cache.

trace stepHardware

The callee runs its prologue: push rbp (saving the caller's frame pointer) and mov rbp, rsp establish the new frame; sub rsp, N reserves space for locals. The frame — return address, saved registers, locals, spilled temporaries — is just the region between rbp and rsp. Allocating it is a single arithmetic instruction: no malloc, no syscall, no page faults. The stack grows on demand and the kernel has already mapped the region lazily.

trace stepHardware

The body runs. Locals that fit in registers never touch memory; spills go to the frame. The stack's locality is its superpower: the frame just allocated is in the same cache lines the caller was just touching. Everything here — loads, stores, arithmetic — executes at L1 latency (~1 ns) unless it spills to L2/L3.

trace stepHardware

The epilogue reverses the prologue: leave restores rsp and rbp in one instruction, and ret pops the return address and jumps back to the caller. The frame is dead — its memory isn't cleared or freed, it's simply abandoned; the next call will overwrite it. Total cost of the whole round trip: roughly 1–5 ns for a hot call with register args — a handful of instructions, one store, one load.

bash
objdump -d /bin/true | grep -A5 "<main>"
perf stat -e cycles,instructions,mem_inst_retired.all_loads true

What the machine actually does on every call is a tiny, fixed-cost dance: move arguments, push one address, adjust one pointer, run, restore, pop, jump back. There is no allocator involved, no locking, no bookkeeping — which is exactly why a billion calls per second per core is routine, and why any optimization that avoids a call (inlining, tail calls) is measured in nanoseconds, not milliseconds.