Why does the compiler convert while and for loops into do-while form internally?
Because do-while — "run the body, then conditionally jump back" — is the only loop shape the hardware directly provides; everything else is built on top of it.
Hardware offers straight-line execution plus conditional branches. The do-while pattern maps onto that with zero overhead: emit the body, then one conditional backward jump.
.L_loop:
# Body
cmpq ...
jne .L_loop # body, then a single conditional jump back
while and for differ only in that they must not run the body when the condition is false on entry. The compiler handles that by reusing the do-while body and adding one guard test in front:
if (!Test) goto done; # guard: skip the whole loop if false up front
loop:
Body
if (Test) goto loop;
done:
Optimization payoff: if the compiler can prove the first test always passes — e.g. for (i=0; i<N; i++) with constant N > 0 — it deletes the guard, leaving the leanest possible do-while. This "single back-edge" structure is also what loop optimizers and CPU branch predictors handle best.
Go deeper:
Do-while loop (Wikipedia) — the do-while target form.
Control flow (Wikipedia) — loops as control flow.