Перейти к содержанию

Условные переходы и регистр флагов

Управление потоком исполнения в ассемблере реализуется через инструкции перехода. Безусловные переходы (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

if (x > 0) { /* positive */ } else { /* non-positive */ }
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

while (x > 0) { x--; }
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

for (int i = 0; i < 10; i++) { /* ... */ }
    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 и переходит, если он не стал нулём:

    movq $10, %rcx
loop_start:
    # тело цикла ...
    loop loop_start         # rcx--; if rcx != 0, jmp loop_start

Однако на современных процессорах loop работает медленнее, чем явная пара dec/jnz, поэтому компиляторы её почти не генерируют.

Инструкция cmov

Семейство инструкций cmov (conditional move) позволяет избежать условных переходов совсем, выполняя пересылку только при соблюдении условия:

cmpq %rbx, %rax
cmovg %rbx, %rax       # rax = rbx, только если rax > rbx (без перехода!)

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);.

Связанные темы

Источники

  • 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/