Проверка сложных условий

Последнее обновление: 18.07.2023

Когда условие очень простое, то его довольно легко описать на языке ассемблера. Однако если условие комплексное и состоит из ряда простых, то это может привести к значительному усложнению кода ассемблера. Обычно условия связаны операциями AND (И) и OR (ИЛИ).

Операция AND

Например, возьмем условие, которое состоит из двух подусловий, объединенных операцией AND:

if((x1 == x2) && (x3 != x4)) {действия}

то есть, условие истинно, если одновременно a равно b и c не равно d. Фактически мы можем представить это выражение как:

if(x1 == x2) if(x3 != x4)  {действия}

С точки зрения ассемблера нам надо последовательно проверить все условия, которые связаны операцией AND:

.global _start

_start:
    mov x0, #1
    mov x1, #22     
    mov x2, #22    
    mov x3, #24
    mov x4, #25       

begin_if:

    cmp x1, x2      // проверяем первое условие (x1==x2)
    b.ne end_if     // если первое условие не верно
     
    cmp x3, x4      // проверяем второе условие (x3!=x4)
    b.eq end_if      // если второе условие не верно
 
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

То есть проверяем первое условие. Если оно неверно, переходим к концу конструкции if. Если первое условие верно, переходим к проверке второго условия. Если второе условие неверно, переходим к концу конструкции if. Если второе условие верно, выполняем указанные действия.

Хотя мы так можем писать, но для проверки комбинированных условий в ассемблере ARM64 мы можем использовать инструкцию CCMP, которая рассматривалась в статье Инструкция CCMP. Объединение условий

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #22    
    mov x3, #24
    mov x4, #25       

begin_if:

    cmp x1, x2      // проверяем первое условие (x1==x2)
    ccmp x3, x4, 0b0100, eq // проверяем второе условие (x3!=x4)
    b.eq end_if      // если второе условие не верно, выход из конструкции if
 
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Инструкция CCMP сравнивает регистры Х3 и Х4, если только первое условие (Х1==Х2) верно, то есть имеем условие EQ.

ccmp x3, x4, 0b0100, eq

Если второе условие НЕ верно, выполняется переход к метке end_if - к концу конструкции if:

b.eq end_if

Если же первое условие не верно, то сравнения Х3 и Х4 не происходит, и на этот случай нам надо указать значения для флагов состояния NZCV - в данном случае это число 0b0100. То есть если первое условие неверно, флаги будут установлены следующим образом: N=0, Z=1, C=0, V=0. Флаг нуля устанавливается в 1. Почему? Потому что благодаря подобной установки флагов последующая инструкция b.eq end_if переходит к метке end_if.

Операция OR

Другой распространенный способ объединения условий представляет операция OR или логическое ИЛИ:

if((x1 == x2) || (x3 != x4)) {действия}

то есть, условие истинно, если или x1 равно x2, или x3 не равно x4. Фактически мы можем представить это выражение как:

if(x1 == x2) {действия}
else if(x3 != x4) {действия}

Если верно одно из условий, то выполняются те же самые действия.

С точки зрения ассемблера нам надо последовательно проверить все условия, которые связаны операцией OR:

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #23    
    mov x3, #24
    mov x4, #25       

begin_if:

    cmp x1, x2      // проверяем первое условие (x1==x2)
    b.eq do_if

    cmp x3, x4       // проверяем второе условие (x3!=x4)
    b.eq end_if      // если второе условие не верно, выход из if
do_if:
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Сначала проверяем первое условие. Если оно верно, переходим к выполнению действий, а второе условие нам нет смысла проверять. Если первое условие не верно, проверяем второе условие. Если второе условие верно, переходим к выполнению действий. Если второе условие тоже не верно, переходим к концу конструкции.

Опять же для проверки подобного составного условия мы можем применить инструкцию CCMP:

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #22    
    mov x3, #24
    mov x4, #25       

begin_if:

    cmp x1, x2      // проверяем первое условие (x1==x2)
    ccmp x3, x4, 0, ne // проверяем второе условие (x3!=x4), если первое условие НЕ верно
    b.eq end_if      // если второе условие не верно, выход из if
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Здесь инструкция CCMP сравнивает регистры Х3 и Х4, если только предыдущее условие не верно (так как если первое условие верно, нет смысла проверять второе условие). Таким образом, CCMP, если выполняется условие NE (Х1 != Х2):

ccmp x3, x4, 0, NE

Если второе условие тоже НЕ верно, переходим к концу конструкции if:

b.eq end_if

На случай если первое условие верно (Х1 == Х2), то нам надо установить флаги так, при котором НЕ верно второе условие, и выполняется переход b.eq end_if. Поэтому устанавливаем для всех флагов значение 0.

AND и OR

На основе выше указанной информации мы можем переложить на ассемблер более сложные условия, например, которые сочетают операции AND и OR:

if(((x1 == x2) && (x3 != x4)) || (x5 == x6)) {действия}

В данном случае общее условие истинно, если либо верно подусловие (x1 == x2) && (x3 != x4), либо (x5 == x6). При составлении программы на языке ассемблера целесообразнее вначале проверить третье условие:

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #22    
    mov x3, #23
    mov x4, #24   
    mov x5, #25
    mov x6, #25       

begin_if:
    cmp x5, x6     // x5 == x6
    b.eq do_if     // если третье условие верно переходим к do_if
    
    cmp x1, x2      // (x1==x2)
    b.ne end_if     // если первое условие не верно
    
    cmp x3, x4      // x3!=x4
    b.eq end_if      // если второе условие не верно

do_if:
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

То есть вначале проверяем третье условие. Если оно верно, то проверка первых двух условие не имеет смысла, и поэтому переходим к выполнению действий в метку do_if. Если третье условие не верно, то последовательно проверяем первое и второе условие. Если одно из них неверно, то переходим к концу конструкции в метке end_if.

Если нам важен порядок выполнения, то мы могли бы написать так:

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #22    
    mov x3, #23
    mov x4, #23   
    mov x5, #25
    mov x6, #25       

begin_if:

    cmp x1, x2      // (x1==x2)
    b.ne or_if     // если первое условие не верно
    
    cmp x3, x4      // x3!=x4
    b.ne do_if      // если второе условие не верно

or_if:
    cmp x5, x6     // x5 == x6
    b.ne end_if     // если третье условие не верно, переходим к end_if
do_if:
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Проверяем первое условие. Если оно неверно, переходим к метке or_if к проверке третьего условия. Если первое условие верно, переходим к проверке второго условия. Если второе условие верно, то проверять третье условие смысла нет, и переходим к метке do_if и выполнению указанных инструкций.

Выполнение подобной конструкции с помощью CCMP:

.global _start

_start:
    mov x0, #1 
    mov x1, #22     
    mov x2, #22    
    mov x3, #23
    mov x4, #24   
    mov x5, #25
    mov x6, #25       

begin_if:

    cmp x1, x2      // проверяем первое условие (x1==x2)
    ccmp x3, x4, 0b0100, eq // проверяем второе условие (x3!=x4), если первое условие верно
    ccmp x5, x6, 0b0100, eq // проверяем третье условие (x5==x6), если второе условие НЕ верно
    b.ne end_if      // если второе условие не верно, выход из if
    add x0, x0, #1      // если оба условия верны - действия конструкции if
end_if:
    mov x8, #93         // номер функции Linux для выхода из программы - 93
    svc 0               // вызываем функцию и выходим из программы

Чтобы лучше понять набор инструкций CMP/CCMP рассмотрим поэтапно

  1. cmp x1, x2

    Если условие верно: Х1==Х2, то флаг Z = 1. Если условие не верно, то Z=0

  2. ccmp x3, x4, 0b0100, eq

    Здесь нам надо, чтобы первое условие было верно, то есть собслюдался код условия EQ (Z = 1). Если Z=1, то сравниваем X3 и X4. Если второе условие верно: Х3 != Х4, то флаг Z = 0. Если условие НЕ верно, то Z=1

    Но если первое условие не верно, и соответственно второе сравнение не выполняется, то нам надо установить флаги так, чтобы выполнялось третье сравнение. Третье сравнение должно выполняться, если второе условие НЕ верно, то есть установлен флаг Z=1. Поэтому третим операндом инструкции выступает число 0b0100, которое устанавливает для флага Z

  3. ccmp x5, x6, 0b0100, eq

    Для выполнения сравнения Х5 и Х6 надо, чтобы второе условие было НЕ верно, ложно, так как если второе условие верно, проверять третье условие не имеет смысла. Ложность второго условия означает, что то есть соблюдается код условия EQ (Z = 1). Если Z=1, то сравниваем X5 и X6. Если третье условие верно: Х5 == Х6, то флаг Z = 0. Если условие не верно, то Z=1

    Если третье условие НЕ верно (Z=0), то происходит переход к метке:

    b.ne end_if

    Если второе условие (а точнее комбинация первого и второго условий) верно, и третье сравнение не выполняется, то опять же нам надо установить флаги. Далее проверяется результат третьего сравнения:

    Если первое+второе условия верны, то нам надо установить флаг Z таким образом, чтобы в инструкции b.ne end_if не было перехода к метке end_if, то есть, чтобы Z=1. Так общее условие истинно, верно. Поэтому опять в качестве третьего операнда инструкции CCMP передаем число 0b0100.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850