roadmap
week 2 · day 10

Loops at the Instruction Level

for and while are just labels and jumps

Easy 30 min 180 xp
After this you can
  • Explain why hardware has no 'loop' — only compare, jump, and a counter you maintain
  • Translate a for and a while loop into a label + body + conditional back-edge
  • Trace a loop's registers by hand across the first iterations
  • Recognize the loop shape in disassembly and reconstruct the source loop

There is no loop instruction (that matters)

Yesterday: no "if," only cmp + jump. Same story here — there is no real "loop" in the machine. A loop is just a jump that goes backward. You place a label, run a body, and conditionally jump *back up* to the label. The only extra ingredient is a counter you increment yourself and test each time. for, while, do-while — all three are this one idea with the pieces arranged slightly differently.

A while loop, taken apart

Start with the most literal shape:

count.cc
1int i = 0;
2while (i < 5) {
3 // body
4 i++;
5}
while, as label + test + back-edgeasm
1 mov ecx, 0 ; i = 0 (i lives in ecx)
2.loop:
3 cmp ecx, 5 ; test i < 5
4 jge .done ; if i >= 5, leave the loop
5 ; --- body goes here ---
6 inc ecx ; i++
7 jmp .loop ; back-edge: jump UP to re-test
8.done:

Four parts, and every loop has them: an init (set the counter), a test at the top (cmp + conditional jump *out*), the body, and the back-edge (jmp up to the test). The word "loop" never appears — it's a downward test and an upward jump around a body. The label .loop is the target the back-edge returns to; .done is the exit.

Key idea
The signature of a loop in disassembly is a backward jump — a jmp (or conditional jump) whose target address is *lower* than the jump itself. Spot a backward jump and you've found a loop; the label it targets is the loop top, and the conditional jump that can skip past it is the exit test.

A for loop is the same, just gathered up

for (init; test; step) is literally a while with the three controls named in one line:

sum.cc
1int sum = 0;
2for (int i = 0; i < 4; i++) {
3 sum += i;
4}
the for loop, compiled (conceptually)asm
1 xor eax, eax ; sum = 0 (xor reg,reg is the idiom for 'set to 0')
2 xor ecx, ecx ; i = 0
3.loop:
4 cmp ecx, 4 ; test i < 4
5 jge .done
6 add eax, ecx ; sum += i
7 inc ecx ; i++
8 jmp .loop
9.done: ; sum (in eax) = 0+1+2+3 = 6
Note
xor eax, eax sets eax to 0 (anything XOR itself is 0). Compilers use it instead of mov eax, 0 because it's smaller and the CPU treats it specially — you'll see it constantly, so read it as "zero this register."

Trace it, don't trust it

Understanding a loop means being able to *predict its state*. Here's the for above, traced by hand for each pass — this is the habit that makes loops (and later, exploits) obvious rather than scary:

hand-trace of the sum loop
  pass | i (ecx) | i<4? | body: sum += i | sum (eax)
  -----+---------+------+----------------+----------
   1   |   0     | yes  | sum = 0+0      |    0
   2   |   1     | yes  | sum = 0+1      |    1
   3   |   2     | yes  | sum = 1+2      |    3
   4   |   3     | yes  | sum = 3+3      |    6
   5   |   4     | NO   | exit           |    6  ← result
Predict first
Change the test to i <= 4 (jg .done becomes... which jump, and how many times does the body run?). With for (int i = 0; i <= 4; i++), how many passes execute the body, and what final sum?
finished reading?
Your task, you write the code

Build a loop, then read one

Two parts. (1) In loop.asm, write a loop that sums the integers 1..10 into a register using the init/test/body/back-edge skeleton from this lesson, and exit with that sum as the exit code (echo $? → 55). (2) Write the same sum as a C for-loop, compile at -O0, disassemble, and find the four parts (init, top test, body, back-edge) plus the backward jump. Everything needed is here: cmp, a conditional jump out, add/inc, and a jmp back.

deliverable: loop.asm (+ the C version's annotated disassembly)
build & run
$ nasm -f elf64 loop.asm -o loop.o && ld loop.o -o loop
$ ./loop; echo $? # expect 55
$ gcc -O0 -g loopc.c -o loopc && objdump -d -M intel loopc
self-review before running
  • Your assembly loop has a clear init, top test, body, and backward jmp
  • echo $? prints 55
  • In the C disassembly you identified the backward jump (the loop's tell)
  • You hand-traced at least the first three iterations before trusting it
stretchCompile your C loop at -O2 and see how the optimizer transforms it — it may unroll it, or even compute the sum with a formula and emit no loop at all. Explain what it did and why the result is still 55.

Self-check

01What makes a loop, at the instruction level?
02In disassembly, the giveaway that you're looking at a loop is:
03Why does `xor eax, eax` appear where you expected `mov eax, 0`?
04Changing `i < 4` to `i <= 4` in a for-loop does what at the machine level?
0/4 correct · 0/4 checked