The Desire: Conditional branching.
The Physical Axiom (Axiom 4 — Monotonic Execution):
The CPU cannot "choose" a block of code directly.
It can only do one of two things:
1) Continue to the next sequential address.
2) Overwrite %rip with a target address (a Jump).
To decide whether to jump, the CPU maintains a special 1-bit register collection called Condition Codes (or the EFLAGS register):
Whenever the ALU executes an operation (like sub, add, cmp),
these 1-bit flags are updated automatically as side effects:
| Flag | Name | Mathematical Definition | Hardware Meaning |
|---|---|---|---|
ZF | Zero Flag | The operation produced a zero (e.g., , so ). | |
SF | Sign Flag | The most significant bit (MSB) of the result is 1 (negative). | |
OF | Overflow Flag | Two's-complement signed overflow occurred. | |
CF | Carry Flag | An unsigned addition carried out of the MSB, or a borrow occurred. |
Crucial Rule: leaq does not alter condition codes. Pure arithmetic (addq, subq, cmpq, testq) does.
Every conditional construct in C is compiled into this exact 3-step sequence:
[Step 1: Set Flags] ───> cmpq %rsi, %rdi (Compute %rdi - %rsi, discard result, set flags)
[Step 2: Read Flags] ───> jg .L_greater (Jump if ZF=0 and SF=OF)
[Step 3: Fallthrough] ───> ... (Code executed if false)
long max(long a, long b) {
if (a > b) return a;
else return b;
}
cat test3_6.s
.file "test3_6.c"
.text
.globl max
.type max, @function
max:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -8(%rbp), %rax
cmpq -16(%rbp), %rax
jle .L2
movq -8(%rbp), %rax
jmp .L3
.L2:
movq -16(%rbp), %rax
.L3:
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size max, .-max
.ident "GCC: (Ubuntu 13.3.0-ubuntu2~24.04.1) 13.3.0"
.section .note.GNU-tack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
+-----------------------------------+
| Function Entry |
| pushq %rbp |
| movq %rsp, %rbp |
| movq %rdi, -8(%rbp) (save a)|
| movq %rsi, -16(%rbp) (save b)|
+-----------------------------------+
|
v
+-----------------------------------+
| Condition |
| movq -8(%rbp), %rax (%rax=a)|
| cmpq -16(%rbp), %rax (a - b) |
+-----------------------------------+
|
jle .L2 (a <= b)
/ \
[ True ] / \ [ False ]
/ \
v v
+----------------------------+ +----------------------------+
| .L2 (Else Block) | | Then-Fallthrough |
| movq -16(%rbp), %rax | | movq -8(%rbp), %rax |
| (%rax = b) | | (%rax = a) |
+----------------------------+ | jmp .L3 |
| +----------------------------+
\ /
\ /
-----> .L3 <----
|
v
+-----------------------------------+
| Function Exit |
| popq %rbp |
| ret |
+-----------------------------------+
[Input Registers] ──────────> [%rdi = a] [%rsi = b]
│ │
▼ ▼
[Stack Memory Frame] ───────> [-8(%rbp)] [-16(%rbp)]
│ │
▼ ▼
[ALU Operation] ────────────> cmpq calculates: (%rax - %rsi) = (a - b)
│
▼
[Flags Register EFLAGS] ────> Updates ZF, SF, OF, CF
│
▼
[Decision Point] ───────────> Does a <= b hold? ((SF ^ OF) | ZF == 1)
├── YES ──> Jump to .L2 ──> Load b into %rax
└── NO ──> Fallthrough ──> Load a into %rax ──> Jump to .L3
cat test3_6_opt.s
.file "test3_6.c"
.text
.p2align 4
.globl max
.type max, @function
max:
.LFB0:
.cfi_startproc
endbr64
cmpq %rsi, %rdi
movq %rsi, %rax
cmovge %rdi, %rax
ret
.cfi_endproc
.LFE0:
.size max, .-max
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
+----------------------------------------+
| Function Entry |
| (No stack setup, no memory writes) |
| Arguments already in registers: |
| %rdi = a, %rsi = b |
+----------------------------------------+
|
v [Monotonic Flow: %rip advances]
+----------------------------------------+
| 1. Comparison |
| cmpq %rsi, %rdi |
| Computes: (%rdi - %rsi) = (a - b) |
| Sets flags: SF, OF, ZF in %rflags |
+----------------------------------------+
|
v [Monotonic Flow: %rip advances]
+----------------------------------------+
| 2. Default Assignment |
| movq %rsi, %rax |
| State: %rax = b |
+----------------------------------------+
|
v [Monotonic Flow: %rip advances]
+----------------------------------------+
| 3. Conditional Move |
| cmovge %rdi, %rax |
| Condition: (SF ^ OF) == 0 (i.e. a>=b) |
| |
| [ a >= b ]: %rax <-- %rdi (value a) |
| [ a < b ]: %rax unchanged (value b) |
+----------------------------------------+
|
v [Monotonic Flow: %rip advances]
+----------------------------------------+
| Function Exit |
| ret (Returns value in %rax) |
+----------------------------------------+
[Compare] ──> [Speculative Load] ──> [Conditional Select] ──> [Return]
The instruction stream never branches. The CPU pipeline executes in a straight line without stalling.