08 — Control Flow¶
Control flow directs the CPU to execute instructions in non-sequential order. In assembly, this is done explicitly with jumps. There are no if/else/for constructs — you build them yourself.
Unconditional Jump¶
Equivalent to goto in C. Used to implement loops and skip blocks.
Conditional Jumps¶
Conditional jumps check the flags register and jump only if the condition is met.
Jumps After CMP (most common)¶
| Instruction | Condition | Flags Checked | C Equivalent |
|---|---|---|---|
je / jz |
equal / zero | ZF=1 | == |
jne / jnz |
not equal / not zero | ZF=0 | != |
jl / jnge |
less than (signed) | SF≠OF | < (signed) |
jle / jng |
less or equal (signed) | ZF=1 or SF≠OF | <= (signed) |
jg / jnle |
greater than (signed) | ZF=0 and SF=OF | > (signed) |
jge / jnl |
greater or equal (signed) | SF=OF | >= (signed) |
jb / jnae |
below (unsigned) | CF=1 | < (unsigned) |
jbe / jna |
below or equal (unsigned) | CF=1 or ZF=1 | <= (unsigned) |
ja / jnbe |
above (unsigned) | CF=0 and ZF=0 | > (unsigned) |
jae / jnb |
above or equal (unsigned) | CF=0 | >= (unsigned) |
Other Flag-Based Jumps¶
| Instruction | Condition |
|---|---|
js |
Sign flag set (result negative) |
jns |
Sign flag clear |
jo |
Overflow flag set |
jno |
Overflow flag clear |
jc |
Carry flag set |
jnc |
Carry flag clear |
jp |
Parity flag set (even parity) |
jnp |
Parity flag clear |
Implementing if / else¶
C Source¶
Assembly Translation¶
cmp rax, 10
jle .else ; if rax <= 10, jump to else
.if:
mov rbx, 1
jmp .end
.else:
mov rbx, 0
.end:
; continue...
Branchless Alternative (CMOVcc)¶
Eliminates branch misprediction penalties.
Implementing Loops¶
While Loop¶
Do-While Loop (more natural in assembly)¶
One fewer comparison per iteration — prefer do-while when at least one iteration is guaranteed.
For Loop¶
// C: sum = 0; for (i = 0; i < 10; i++) sum += i;
int sum = 0;
for (int i = 0; i < 10; i++) sum += i;
xor rax, rax ; sum = 0
xor rcx, rcx ; i = 0
.for:
cmp rcx, 10
jge .endfor
add rax, rcx ; sum += i
inc rcx
jmp .for
.endfor:
LOOP Instruction¶
A legacy x86 instruction that decrements RCX and jumps if not zero:
LOOPis slow on modern CPUs (microcoded). Prefer explicitdec rcx; jnz .loop.
Implementing switch / case¶
// C
switch (rax) {
case 0: /* do A */; break;
case 1: /* do B */; break;
case 2: /* do C */; break;
default: /* do D */;
}
Chain of CMP/JE (small ranges)¶
cmp rax, 0
je .case0
cmp rax, 1
je .case1
cmp rax, 2
je .case2
jmp .default
.case0:
; do A
jmp .end_switch
.case1:
; do B
jmp .end_switch
.case2:
; do C
jmp .end_switch
.default:
; do D
.end_switch:
Jump Table (dense ranges, compiler-preferred)¶
section .data
jump_table dq case0, case1, case2 ; array of addresses
section .text
; Bounds check
cmp rax, 2
ja .default
lea rdx, [jump_table]
mov rdx, [rdx + rax*8] ; load handler address
jmp rdx ; indirect jump
.case0: ; ...
.case1: ; ...
.case2: ; ...
.default: ; ...
Jump tables give O(1) dispatch regardless of case count — compilers generate these automatically for large, dense switch statements.
SETcc — Set Byte on Condition¶
Sets a byte register/memory to 0 or 1 based on a flag condition:
cmp rax, rbx
sete al ; al = 1 if rax == rbx, else 0
setl al ; al = 1 if rax < rbx (signed)
setb al ; al = 1 if rax < rbx (unsigned)
Useful for converting comparison results into integer booleans.
Label Naming Conventions¶
NASM labels follow these conventions:
_start: ; global symbol (exported, no leading dot)
global _start
.loop: ; local label (scope limited to surrounding global label)
.end: ; another local label
myfunction: ; non-exported function label
Local labels starting with . are only visible within the surrounding global label scope, preventing name clashes:
Complete Example: FizzBuzz (1–20)¶
; fizzbuzz.asm — print numbers 1-20 with FizzBuzz rules
; (simplified: just exits with the last classification in rax)
; 0 = FizzBuzz, 1 = Fizz, 2 = Buzz, 3 = number
section .text
global _start
_start:
mov r12, 1 ; counter = 1
.loop:
cmp r12, 20
jg .done
mov rax, r12
xor rdx, rdx
mov rbx, 15
div rbx
test rdx, rdx
jz .fizzbuzz ; divisible by 15 → FizzBuzz
mov rax, r12
xor rdx, rdx
mov rbx, 3
div rbx
test rdx, rdx
jz .fizz ; divisible by 3 → Fizz
mov rax, r12
xor rdx, rdx
mov rbx, 5
div rbx
test rdx, rdx
jz .buzz ; divisible by 5 → Buzz
; else: just a number
jmp .next
.fizzbuzz:
jmp .next
.fizz:
jmp .next
.buzz:
jmp .next
.next:
inc r12
jmp .loop
.done:
mov rax, 60
xor rdi, rdi
syscall
Key Takeaways¶
- Set flags with
CMP(comparison) orTEST(bitwise check), then branch withJcc - Use signed jumps (
jl,jg) for signed integers; unsigned jumps (jb,ja) for unsigned - Do-while loops are more natural in assembly (one less comparison per iteration)
CMOVccavoids branches altogether — good for performanceSETccconverts a condition into a 0/1 byte value- Jump tables provide O(1) dispatch for switch-like patterns