Comprender las banderas y los saltos condicionales de x86-64
CMP y TEST, condiciones con y sin signo y las decisiones del código sin ramas
Comprender las banderas y los saltos condicionales de x86-64
Estos artículos del blog se han creado con fines didácticos. Los ejemplos usan sintaxis Intel. El rendimiento debe medirse en la CPU objetivo con la distribución real de entradas.
cmp eax, edx no guarda un resultado visible. ¿Cómo sabe el siguiente jl qué valor es menor? ¿Por qué los mismos bits 0xFFFFFFFF pueden ser -1 o 4.294.967.295? La respuesta está en RFLAGS y en la condición signed o unsigned que consume esas banderas.
Banderas habituales
| Bandera | Significado |
|---|---|
ZF | el resultado fue cero |
CF | carry/borrow unsigned |
SF | bit más alto del resultado |
OF | el resultado signed excedió su rango |
PF | paridad del byte inferior |
Las comparaciones normales combinan sobre todo ZF, CF, SF y OF.
cmp y test
cmp rax, rdx
Conceptualmente calcula rax-rdx, descarta el valor y actualiza solo las banderas.
cmp eax, 10
je equal_ten
jl signed_less_than_ten
Un salto no está ligado gramaticalmente al cmp más cercano. Si una instrucción intermedia cambia las banderas, el salto observa el nuevo estado.
cmp eax, 10
add ecx, 1
jl somewhere ; observa ADD
test funciona como un AND cuyo resultado se descarta; es común en pruebas de cero y máscaras.
test rax, rax
jz is_zero
test eax, 1
jnz is_odd
Igualdad y orden
La igualdad no depende del signo: je/jz prueba ZF=1 y jne/jnz prueba ZF=0. El orden sí debe separarse.
| Significado | signed | unsigned |
|---|---|---|
| left < right | jl | jb |
| left <= right | jle | jbe |
| left > right | jg | ja |
| left >= right | jge | jae |
mov eax, 0FFFFFFFFh
cmp eax, 1
jl signed_less ; -1 < 1, verdadero
jb unsigned_below ; 4294967295 < 1, falso
El registro no lleva una etiqueta signed. La interpretación de las mismas banderas produce la diferencia.
CF y OF describen desbordamientos distintos
En ocho bits, 0xFF+1=0x00 tiene carry unsigned, pero signed -1+1 es correcto. 0x7F+1=0x80 es unsigned 128, pero excede el máximo signed 127.
add al, 1
jc unsigned_overflow
jo signed_overflow
C/C++ define la aritmética unsigned como módulo; el overflow signed es comportamiento indefinido. La existencia de OF no cambia el contrato del lenguaje. Además, inc y dec conservan CF, de modo que no son idénticos a add 1 y sub 1 para todas las banderas.
setcc y cmovcc
xor eax, eax
cmp edi, esi
setl al
ret
Convierte la comparación signed en 0 o 1. setcc solo escribe un byte, así que hay que limpiar conscientemente la parte superior.
cmp eax, edx
cmovl eax, edx
Un movimiento condicional selecciona sin bifurcar el control, pero puede alargar dependencias. No es automáticamente más rápido.
Branchless no significa más rápido
Las CPU modernas predicen saltos. Un jcc predecible es barato; con datos irregulares y caminos pequeños, setcc, cmovcc o máscaras pueden ayudar. Hay que medir distribución real, fallos de predicción, trabajo de ambos caminos, instrucciones y dependencias añadidas, y salida del compilador en la CPU objetivo.
Bucles y cortocircuito
xor eax, eax
xor edx, edx
loop_test:
cmp rdx, rsi
jae loop_done
add eax, [rdi + rdx*4]
inc rdx
jmp loop_test
loop_done:
ret
jae comprueba unsigned i >= count. La optimización puede rotar, desenrollar o vectorizar el bucle.
test rcx, rcx
jz done
cmp byte ptr [rcx + READY_OFFSET], 0
je done
call use
done:
Es una posible forma de p != nullptr && p->ready. El primer salto evita desreferenciar null; el orden forma parte de la seguridad.
Tabla de saltos
sub eax, 3
cmp eax, 4
ja default_case
jmp qword ptr [table + rax*8]
Tras normalizar los casos, una comprobación unsigned limita el índice antes del salto indirecto. Es también una frontera de seguridad importante.
No conservar flags a través de llamadas
cmp eax, edx
call helper
jl old_result ; helper puede cambiar los flags
Convierta la condición en 0/1 antes de llamar o vuelva a comparar después.
Resumen
cmp y test producen banderas sin guardar su resultado. La igualdad usa ZF; el orden separa las condiciones signed SF/OF de las unsigned CF/ZF. Siga la última instrucción que definió las banderas, el signedness de la condición y el rango posible en cada arista de control.