How Loops Compile
How for and while loops become a label, a body, and a back-edge jump.
A Loop Is Just a Jump Backwards
Every loop you have ever written reduces to one new idea on top of what you already know: a jump that points backwards, to a label earlier in the listing. Forward jumps skip code; a backward jump re-runs it. That backward jump is called the back-edge, and spotting it is how you recognise a loop in assembly. Everything else, the counter, the condition, the body, is built from the cmp and jumps you already understand.
Let us read a for loop and find the back-edge.
A for Loop at -O0
Here is a function that sums the integers from 1 to n:
int sumTo(int n)
{
int total{0};
for (int i{1}; i <= n; ++i)
total += i;
return total;
}
At -O0:
sumTo(int):
push rbp
mov rbp, rsp
mov DWORD PTR -20[rbp], edi
mov DWORD PTR -4[rbp], 0
mov DWORD PTR -8[rbp], 1
jmp .L2
.L3:
mov eax, DWORD PTR -8[rbp]
add DWORD PTR -4[rbp], eax
add DWORD PTR -8[rbp], 1
.L2:
mov eax, DWORD PTR -8[rbp]
cmp eax, DWORD PTR -20[rbp]
jle .L3
mov eax, DWORD PTR -4[rbp]
pop rbp
ret
Take it slowly; this layout is worth fully understanding because almost every loop follows it.
mov DWORD PTR -20[rbp], edistores the argumentn.mov DWORD PTR -4[rbp], 0initialisestotalto 0.mov DWORD PTR -8[rbp], 1initialises the counterito 1. This is the loop's init step, and it runs exactly once.jmp .L2jumps forward, straight to the condition. Hold that thought..L3:is the top of the loop body. The twoaddinstructions aretotal += iand++i..L2:is the condition. It loadsi, compares it withn, andjle .L3jumps back to the body ifi <= n.
That jle .L3 is the back-edge: a conditional jump to a label above it. As long as the condition holds, control keeps looping back to .L3. When i finally exceeds n, the jle is not taken, execution falls through, and the function returns total.
The Bottom-Test Pattern
Look again at the very first jump, jmp .L2. The compiler did something that looks odd at first: before running the body even once, it jumps straight down to the condition. This is the bottom-test layout, and it is the standard shape for loops.
Why structure it this way? Putting the condition at the bottom means the back-edge is a single conditional jump, jle .L3, that does double duty: it both tests the condition and loops back. The alternative (condition at the top) would need a conditional jump forward to exit and an unconditional jump back at the end, two branches per iteration instead of one. The bottom-test version is cheaper, so compilers emit it by default.
But a for loop must check its condition before the first iteration too, in case the body should not run at all (imagine sumTo(0), where 1 <= 0 is false from the start). The initial jmp .L2 solves this neatly: it sends control to the condition first, so the body at .L3 is entered only if the test passes. After that first check, every iteration ends by falling into .L2 and looping back through the same test.
So the canonical loop shape is:
... init ... ; runs once (e.g. i = 1)
jmp .Lcond ; check the condition before the first iteration
.Lbody:
... loop body ... ; the work, plus the increment
.Lcond:
cmp / test ; evaluate the loop condition
j<cond> .Lbody ; the BACK-EDGE: loop again if condition holds
... after the loop ...
When you read an unfamiliar loop, find the conditional jump whose target is above it. That is the back-edge, and its label marks the start of the body. Everything between the back-edge's target and the back-edge itself runs once per iteration.
while Loops Look the Same
A while loop is the same machine with the init and increment removed. Compare:
int countDown(int n)
{
int steps{0};
while (n > 0)
{
n -= 1;
steps += 1;
}
return steps;
}
countDown(int):
push rbp
mov rbp, rsp
mov DWORD PTR -20[rbp], edi
mov DWORD PTR -4[rbp], 0
jmp .L2
.L3:
sub DWORD PTR -20[rbp], 1
add DWORD PTR -4[rbp], 1
.L2:
cmp DWORD PTR -20[rbp], 0
jg .L3
mov eax, DWORD PTR -4[rbp]
pop rbp
ret
The skeleton is identical to the for loop: an initial jmp .L2 to the condition, a body at .L3, the condition at .L2, and a back-edge jg .L3. The only difference is that there is no separate counter init or increment, because the source did not ask for them. This is the deeper lesson: for and while are not really different in assembly. They are surface syntax over the same label-body-condition-backedge structure. Once you can read one, you can read them all.
Notice too that the condition uses jg (signed "greater than"), matching n > 0 on a signed int, exactly the signed/unsigned distinction from the jumps lesson. The loop's exit test is just another conditional jump, and all the flag-reading rules you already know apply unchanged.
Try It Yourself
In the exercise for this lesson you will finish a loop that sums numbers, run it to confirm the output, and then read the assembly panel. Your mission is to locate two specific things: the back-edge (the conditional jump whose target is a label above it) and the loop-condition compare that feeds it. Finding them in code you wrote yourself is the moment loops in assembly stop looking mysterious.
Key Takeaways
- A loop is built from a back-edge: a conditional jump to a label above it that re-runs the body.
- The standard layout is bottom-test: an initial
jmpto the condition, then body, then condition with the back-edge, so each iteration costs only one branch. - The initial
jmpto the condition exists so the loop can run zero times when the condition is false from the start. forandwhilecompile to the same skeleton; aforsimply adds the one-time init and the increment inside the body.- The loop's exit test is an ordinary
cmpplus conditional jump, so signed/unsigned jump families and condition inversion apply just as inifstatements.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
How Loops Compile - Quiz
Test your understanding of the lesson.
Practice Exercises
Sum the Numbers with a Loop
Complete the sumTo function so it uses a for loop to add every integer from 1 up to and including n. The program prints sumTo(1), sumTo(5), sumTo(10), and sumTo(100). Once your output is correct, switch the assembly panel to -O0 and find the loop: locate the back-edge (the conditional jump whose target is a label above it) and the loop-condition compare that feeds it.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!