Условные переходы и регистр флагов¶
Управление потоком исполнения в ассемблере реализуется через инструкции перехода. Безусловные переходы (jmp) всегда
изменяют регистр rip (instruction pointer). Условные переходы (семейство jcc) делают это только при выполнении
определённого условия, которое выражается через состояние регистра флагов RFLAGS.
Регистр флагов RFLAGS¶
RFLAGS — 64-битный регистр, отдельные биты которого устанавливаются как побочный эффект арифметических и логических
инструкций. Именно эти биты анализируют инструкции условного перехода.
RFLAGS (биты 63..0, показаны значимые позиции; биты 63..22 reserved)
21 18 17 16 15 14 12 11 10 9 8 7 6 4 2 0
┌───┬───┬───┬───┬───┬───┬────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ID │AC │VM │RF │ - │NT │IOPL│OF │DF │IF │TF │SF │ZF │AF │PF │CF │
└───┴───┴───┴───┴───┴───┴────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └── CF Carry Flag
│ │ │ │ │ │ └────── PF Parity Flag
│ │ │ │ │ └────────── AF Auxiliary Carry
│ │ │ │ └────────────── ZF Zero Flag
│ │ │ └────────────────── SF Sign Flag
│ │ └────────────────────── TF Trap Flag
│ └────────────────────────── IF Interrupt Enable
└────────────────────────────── DF Direction Flag
OF ──┘ (бит 11) OF Overflow Flag
| Флаг | Аббревиатура | Когда устанавливается в 1 |
|---|---|---|
| Zero Flag | ZF | Результат последней операции равен нулю |
| Carry Flag | CF | Возник беззнаковый перенос или заём |
| Sign Flag | SF | Старший бит результата равен 1 (результат отрицательный) |
| Overflow Flag | OF | Переполнение при знаковой арифметике |
| Parity Flag | PF | Чётное количество единичных битов в результате |
| Auxiliary Carry | AF | Перенос из младшего полубайта (бит 3 → бит 4) |
| Direction Flag | DF | Управляет направлением строковых инструкций |
| Interrupt Enable | IF | Разрешение аппаратных прерываний |
| Trap Flag | TF | Пошаговое исполнение (single-step для отладчиков) |
Флаги устанавливают большинство арифметических и логических инструкций: add, sub, and, or, xor, inc, dec.
Инструкция cmp dst, src вычисляет dst - src только для установки флагов, не записывая результат никуда. Аналогично,
test dst, src выполняет побитовое AND исключительно ради установки флагов.
Инструкции перехода¶
Безусловный переход¶
jmp label # перейти на метку label
jmp *%rax # перейти по адресу, хранящемуся в rax (косвенный прыжок)
Условные переходы¶
Все условные переходы анализируют текущее состояние флагов в RFLAGS. Они не изменяют флаги сами по себе.
Какой флаг (или комбинацию флагов) проверяет каждая инструкция:
Инструкция Синоним Условие перехода Флаги
─────────────────────────────────────────────────────────────────────
je jz равно / ноль ZF=1
jne jnz не равно / не ноль ZF=0
─────────────────────────────────────────────────────────────────────
Знаковые сравнения (после cmp/sub со знаковыми числами):
─────────────────────────────────────────────────────────────────────
jl jnge меньше SF≠OF
jle jng меньше или равно ZF=1 или SF≠OF
jg jnle больше ZF=0 и SF=OF
jge jnl больше или равно SF=OF
js — отрицательное число SF=1
jns — неотрицательное число SF=0
jo — переполнение OF=1
jno — нет переполнения OF=0
─────────────────────────────────────────────────────────────────────
Беззнаковые сравнения (после cmp/sub с беззнаковыми числами):
─────────────────────────────────────────────────────────────────────
jb jc/jnae ниже (unsigned <) CF=1
jbe jna ниже или равно CF=1 или ZF=1
ja jnbe выше (unsigned >) CF=0 и ZF=0
jae jnb/jnc выше или равно CF=0
jc — перенос CF=1
jnc — нет переноса CF=0
─────────────────────────────────────────────────────────────────────
jp jpe чётный паритет PF=1
jnp jpo нечётный паритет PF=0
─────────────────────────────────────────────────────────────────────
Для знаковых сравнений (используют SF, OF, ZF):
je label # equal: ZF=1
jne label # not equal: ZF=0
jl label # less: SF != OF
jle label # less or equal: ZF=1 || SF != OF
jg label # greater: ZF=0 && SF == OF
jge label # greater or equal: SF == OF
Для беззнаковых сравнений (используют CF, ZF):
jb label # below (unsigned <): CF=1
jbe label # below or equal: CF=1 || ZF=1
ja label # above (unsigned >): CF=0 && ZF=0
jae label # above or equal: CF=0
По отдельным флагам:
jz label # zero: ZF=1 (синоним je)
jnz label # not zero: ZF=0 (синоним jne)
js label # sign: SF=1 (результат отрицательный)
jns label # no sign: SF=0
jo label # overflow: OF=1
jno label # no overflow: OF=0
jc label # carry: CF=1
jnc label # no carry: CF=0
Типичная последовательность сравнения и условного перехода:
cmpq %rbx, %rax # вычислить rax - rbx, установить флаги
je equal_label # если ZF=1 (rax == rbx), перейти
# ... код для случая rax != rbx ...
jmp end
equal_label:
# ... код для случая rax == rbx ...
end:
Реализация управляющих конструкций¶
if / else¶
movq x(%rip), %rax # rax = x
testq %rax, %rax # установить флаги по rax (AND с собой)
jle non_positive # если x <= 0, перейти на ветку else
# ветка then: x > 0
# ... код ...
jmp end_if
non_positive:
# ветка else: x <= 0
# ... код ...
end_if:
Инструкция testq %rax, %rax — идиоматический способ проверить, равен ли регистр нулю или отрицателен ли он, без
изменения его значения.
while¶
loop_start:
movq x(%rip), %rax
cmpq $0, %rax
jle loop_end # условие выхода: x <= 0
decq %rax
movq %rax, x(%rip)
jmp loop_start
loop_end:
for¶
movq $0, %rcx # i = 0
for_loop:
cmpq $10, %rcx
jge for_end # если i >= 10, выйти
# тело цикла ...
incq %rcx # i++
jmp for_loop
for_end:
Для небольших счётчиков можно также использовать специализированную инструкцию loop, которая автоматически уменьшает
rcx и переходит, если он не стал нулём:
Однако на современных процессорах loop работает медленнее, чем явная пара dec/jnz, поэтому компиляторы её почти не
генерируют.
Инструкция cmov¶
Семейство инструкций cmov (conditional move) позволяет избежать условных переходов совсем, выполняя пересылку только
при соблюдении условия:
cmov полезен там, где условный переход плохо предсказывается, поскольку не нарушает конвейер. Подробнее о влиянии
ветвлений на производительность — в статье о предсказании ветвлений.
Инструкции setcc¶
Семейство setcc записывает 0 или 1 в байтовый регистр в зависимости от состояния флагов. Удобно для вычисления булевых
значений без ветвлений:
cmpq %rsi, %rdi
setg %al # al = 1, если rdi > rsi (знаковое), иначе 0
sete %al # al = 1, если rdi == rsi, иначе 0
setne %al # al = 1, если rdi != rsi, иначе 0
setb %al # al = 1, если rdi < rsi (беззнаковое), иначе 0
movzbq %al, %rax # нулевое расширение al -> rax
Компилятор GCC часто генерирует setcc при компиляции выражений типа int result = (a > b);.
Связанные темы¶
- Основы ассемблера — регистры, базовые инструкции, адресация
- Предсказание ветвлений — почему
cmovбыстрее условных переходов на случайных данных - Параллелизм на уровне инструкций — как условные переходы влияют на конвейер
Источники¶
man 1 as— описание инструкций GNU Assembler- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2: Instruction Set Reference — описание всех инструкций jcc, cmp, test, setcc
- Agner Fog, "Optimizing subroutines in assembly language": https://agner.org/optimize/