Where we are: from a pile of instructions to a running machine
In the previous guide you met the pieces: a register is a tiny, blazingly fast box of bits inside the CPU; rax and its siblings hold the values being worked on; rsp tracks the top of the stack; and there is an instruction set of low-level operations the chip understands. You also met one special register, rip, the program counter, which holds the address of the next instruction. We laid out the parts. This guide is about the motion — how those parts come alive and actually carry a program forward.
Here is the surprise that makes the whole thing tractable: a CPU has no grand plan. It does not "understand your program" the way you do. It repeats one short, fixed procedure forever, and that procedure is the same whether you are running a text editor, a game, or the operating system itself. That repeated procedure is the fetch–decode–execute cycle — also called the instruction cycle. Everything your computer has ever done is this loop, spun fast enough to look like thought.
The three beats of one instruction
Walk through a single turn of the loop slowly; the names then explain themselves. Fetch: the control unit reads rip, goes to that address in memory, and pulls in the bytes of the next instruction. Decode: it figures out what those bytes mean — which operation, and which registers or values they act on. Execute: it actually performs the work, usually inside the ALU (the arithmetic/logic unit) for things like add and compare, or by touching memory for a load or store. Then it advances rip to the following instruction, and the loop turns again.
Written as one tight loop, the whole CPU reads almost trivially: forever, fetch the instruction bytes at rip; decode them into an operation and its operands; advance rip to point at the next instruction; then execute the work. A jump or branch simply writes a different value into rip, so the next fetch lands somewhere else. That is genuinely the entire control core — four lines that never stop.
Two details in that sketch matter more than they look. First, rip moves forward by the length of the instruction, not by a fixed 1 — on x86-64, instructions are variable-length (anywhere from 1 to 15 bytes), so the decode step must also work out how long this instruction was before the next fetch knows where to look. Second, the only way control ever "goes somewhere" — a loop, an `if`, a function call — is by the execute step writing a new address into rip. There is no other mechanism. A jump is just an assignment to the program counter.
Watching the loop run, one beat at a time
Abstractions sink in when you trace real bytes, so let us hand-run a tiny stretch of code. Suppose memory holds three instructions starting at address 0x1000, and we want to compute the sum of two small numbers. The registers start with rip = 0x1000 and we will watch rax and rip change as the loop turns. Each line below is one full pass of fetch–decode–execute.
address instruction what it does 0x1000 mov rax, 5 rax <- 5 0x1005 add rax, 7 rax <- rax + 7 0x1009 ret return to caller step before fetch@ after 1 rip=0x1000 rax=? 0x1000 rax=5 rip=0x1005 2 rip=0x1005 rax=5 0x1005 rax=12 rip=0x1009 3 rip=0x1009 rax=12 0x1009 (returns) rip=<caller>
Read that table as a flip-book. At step 1, rip points at 0x1000; the CPU fetches `mov rax, 5`, decodes it as "put the constant 5 into rax," executes it, and bumps rip to 0x1005 (the mov was 5 bytes long). At step 2 it fetches `add rax, 7`, and the ALU computes 5 + 7 = 12 into rax. At step 3 it fetches `ret`, which — as you will see in the next guide — pops a return address off the stack into rip, so the next fetch lands back in whoever called us. Nowhere did the chip plan ahead; it just kept turning the same crank.
How the cycle bends: branches, flags, and decisions
A straight line of instructions is fine, but programs need to decide. The cycle handles this without any new machinery — it just lets the execute step choose what to write into rip. A comparison like `cmp rax, 0` does a subtraction but throws the result away, keeping only side-notes about it in the flags register: was the result zero, was it negative, did it overflow. Each is a single bit. The very next instruction, a conditional jump such as `jz target`, reads those flag bits and decides whether to set rip to `target` or to fall through to the following instruction.
That two-step dance — compare to set flags, then a conditional jump to act on them — is how every `if`, `while`, and `for` in your C eventually runs. There is no `if` instruction. The compiler shreds your structured control flow down into compares, flags, and jumps, all riding on the same plain fetch–decode–execute loop. Seeing this is the moment high-level control flow stops feeling like a separate language feature and starts looking like what it is: a convenient way to arrange a few jumps.
- Execute a `cmp` or `test`, which performs an operation purely to set the flag bits (zero, sign, carry, overflow) and discards the numeric result.
- Fetch the next instruction, a conditional jump (for example `je`, `jne`, `jl`, `jg`) — its meaning is entirely "jump only if these particular flags say so."
- If the condition holds, the execute step writes the branch target into rip; if not, rip already points just past the jump, so the loop simply falls through.
- The next fetch reads whatever rip now holds — that single write is the entire mechanism behind every branch, loop, and decision in your program.
Honest footnotes: the loop is real, but the timing diagram is a model
The fetch–decode–execute picture is true and worth trusting — but it is a model of the result, not a literal description of how a modern chip spends its nanoseconds. A real CPU does not finish one instruction before starting the next. It pipelines: while one instruction executes, the next is already being decoded and a third is being fetched, like an assembly line. It even runs instructions out of order and speculates past branches it has not yet resolved, guessing which way a jump will go to keep the line full. The honest claim is narrower and still solid: the machine produces the same result as if it had run your instructions one at a time, in order.
Why does this honesty matter to you, climbing this ladder? Because the gap between the simple model and the real silicon is exactly where two later subjects live. Speculative execution guessing wrong is why some security bugs (the famous Spectre/Meltdown family, 2018) can leak data across boundaries. And the cost of a mispredicted branch — the pipeline has to throw away its guessed work and refill — is a real reason fast code tries to avoid unpredictable jumps. You do not need any of that today. But trust the loop as your mental model, and hold the pipeline as the honest asterisk beneath it.
Where this leaves you
Step back and look at what you can now claim. A program does not run; it is walked, one instruction at a time, by a loop that fetches the bytes at rip, decodes their meaning, executes the work, and advances. Branches, loops, and `if`s are nothing but the execute step choosing a new value for rip, often after a compare leaves a verdict in the flags. The same loop underlies a calculator and a kernel. That is genuinely the whole engine — the rest is just which instructions ride on it.
We left one loose thread hanging twice on purpose: `ret` "pops a return address off the stack," and a function call somehow remembers where to come back to. The next guide pulls that thread. It shows the stack at the instruction level — how rsp and the push/pop instructions carve out space, how a `call` saves the return address and a `ret` restores it, and why this one convention is what lets functions nest and recursion work at all. You have the cycle; next we give it a memory of where it has been.