rflags
Існує спеціальний регістр під назвою rflags.
Його біти поводяться як прапорці для різних умов.
Деякі з них наведено нижче:
| назва | символ | біт |
|---|---|---|
| перенесення | CF | 0 |
| нуль | ZF | 6 |
| знак | SF | 7 |
| переповнення | OF | 11 |
Прапорці в rflags не змінюються напряму.
Натомість їх встановлюють багато різних інструкцій.
Наприклад, ZF встановлюється багатьма арифметичними або порозрядними операціями, коли результат дорівнює нулю.
Однією з найпоширеніших інструкцій для перевірки умов є cmp.
Вона приймає два операнди й оновлює прапорці, але не змінює своїх операндів.
cmp
Інструкція cmp віднімає другий операнд від першого й встановлює прапорці відповідно до результату.
Якщо A - перший операнд, а B - другий:
| прапорець | встановлюється, коли |
|---|---|
| CF | A < B (без знака) |
| ZF | A == B |
| SF | A < B (зі знаком, без переповнення) |
| OF | переповнення під час віднімання чисел зі знаком |
За замовчуванням код у x86-64 виконується послідовно, згори вниз.
Але є багато ситуацій, коли цю поведінку потрібно змінити. Наприклад, щоб виконати інший набір інструкцій у відповідь на умову.
У мовах вищого рівня для цього зазвичай використовують такі абстракції, як умовні конструкції if...else.
Однак у x86-64 їх немає.
Натомість x86-64 надає інструкції, які фактично передають виконання в інше місце коду.
Це називається branching.
Ми вже бачили дві такі інструкції: call і ret.
Коли функцію викликають, виконання передається від того, хто викликає, до викликаної функції. А під час повернення виконання передається назад тому, хто викликав.
Якщо ret не знайдено, виконання просто переходить з однієї функції в наступну.
Іноді це можна використати, щоб оптимізувати потік виконання коду.
Інструкція jmp безумовно передає виконання програми в іншу точку коду. Її єдиний операнд - мітка, яка містить адресу точки, з якої виконання продовжиться.
Розгляньмо, наприклад, таку функцію:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
Коли викликають fn, виконання починається з mov rax, 5.
Це встановлює значення rax рівним 5 на цей момент.
Наступна інструкція - jmp end, вона передає виконання на мітку end.
Після end наступна інструкція - ret, вона передає виконання назад у функцію, що викликала.
Зауважмо: оскільки add rax, 10 розташована після jmp end і перед end, вона ніколи не виконується.
Значення rax, коли fn повертає керування, дорівнює 5.
Сімейство інструкцій jcc передає виконання програми в іншу точку лише за виконання певної умови. Інакше виконання продовжується послідовно.
Кожна умова відповідає одному або кільком прапорцям у rflags.
Одні варіанти jcc перевіряють, що прапорець встановлено, інші - що його скинуто.
cc у jcc вжито не буквально: воно вказує на конкретний суфікс, повʼязаний із прапорцем, який перевіряють.
Суфіксів багато, і багато з них перевіряють ту саму умову, що й інший суфікс. Деякі з них указують безпосередньо на прапорець, тож інструкція переходить на мітку, якщо цей прапорець встановлено:
| суфікс | переходить, якщо |
|---|---|
| z | ZF == 1 |
| c | CF == 1 |
| s | SF == 1 |
| o | OF == 1 |
Багато інших вибрано так, щоб вони вказували на свій сенс в інструкції cmp.
Наприклад:
| інструкція | суфікс | переходить, якщо |
|---|---|---|
| cmp A, B | e | A == B |
| cmp A, B | l | A < B (зі знаком) |
| cmp A, B | b | A < B (без знака) |
| cmp A, B | g | A > B (зі знаком) |
| cmp A, B | a | A > B (без знака) |
Можна додати e після l, b, g або a, щоб урахувати рівність в умові:
cmp rcx, r8
jge two ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison
Для всіх суфіксів є варіанти, які перевіряють протилежну поведінку.
Вони мають той самий синтаксис, але з n перед суфіксом.
Наприклад, jnz переходить, коли ZF не встановлено.
Так само jnae переходить, коли A не >= B (A і B тлумачаться як цілі числа без знака).
Деякі суфікси дублюють ті самі умови.
Наприклад, jz (суфікс z, для ZF) і je (суфікс e, для рівності) обидва переходять, коли ZF встановлено.
Це тому, що під час cmp ZF встановлюється, коли віднімання дає нуль, а це відповідає рівності двох операндів.
Однак інші суфікси перевіряють комбінацію прапорців, і їх не можна просто замінити суфіксом одного прапорця.
Краще вибирати суфікс, який точніше описує семантику порівняння.
Мітки видно в усьому файлі вихідного коду, вони не є локальними для функції. Тому повторно використати назву мітки неможливо.
Щоб імітувати поведінку локальної мітки, NASM має спеціальний запис для мітки, оголошеної з крапкою (.) перед нею.
Цей запис визначає мітку, яка неявно включає назву попередньої мітки без крапки:
section .text
fn1:
...
.example: ; this is fn1.example
...
ret
fn2:
...
.example: ; this is fn2.example
...
ret
Усе ще можна перейти на цю мітку з будь-якого місця коду, використавши повну назву мітки, наприклад jmp fn1.example.
Однак перехід, який використовує частину назви мітки, починаючи з крапки, буде виконано на мітку всередині зовнішньої функції.
Наприклад, .example поводиться так, ніби вона локальна для функції:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
У цій вправі ми реалізуємо деякі правила блекджеку, як-от те, як грають і як нараховують очки.
У цій вправі карти позначено числами: кожну числову карту подано її числовим значенням, а валета, даму, короля й туза позначено відповідно як 11, 12, 13 і 14 (джокерів у грі не використовують).
Щоб було зручніше працювати з таким поданням, на початку файлу визначено кілька констант: C2–C10 для числових карт, CJ для валета, CQ для дами, CK для короля, CA для туза.
Передбачається стандартна колода з 52 карт французької масті, але в більшості варіантів для гри перемішують кілька колод разом.
Ось інструкції, згадані в цьому концепті:
| Інструкція | Опис |
|---|---|
| cmp a, b | встановлює прапорці відповідно до a - b |
| jmp a | виконання коду тут зупиняється й продовжується з мітки a |
| jcc a | код продовжується з мітки a, якщо виконано умову в cc
|
Ось умови, які перевіряє jcc після cmp a, b:
| Інструкція | Переходить, коли |
|---|---|
| je | a == b |
| jl | a < b (зі знаком) |
| jg | a > b (зі знаком) |
| jb | a < b (без знака) |
| ja | a > b (без знака) |
| jle | a <= b (зі знаком) |
| jge | a >= b (без знака) |
| jbe | a <= b (без знака) |
| jae | a >= b (без знака) |
| jne | a != b |
| jnl | !(a < b) (зі знаком) |
| jng | !(a > b) (зі знаком) |
| jnb | !(a < b) (без знака) |
| jna | !(a > b) (без знака) |
| jnle | !(a <= b) (зі знаком) |
| jnge | !(a >= b) (зі знаком) |
| jnbe | !(a <= b) (без знака) |
| jnae | !(a >= b) (без знака) |
У блекджеку значення CA дорівнює 1 або 11, залежно від руки (про це далі).
Фігурні карти (CJ, CQ і CK) дають 10 очок, а всі решта карт коштують свого числового значення.
Визначте функцію value_of_card з параметром card, числом, яке позначає карту.
Функція має повертати числове значення переданої карти.
Оскільки CA може набувати кількох значень (1 або 11), поки що зафіксуємо значення CA як 1.
Пізніше ми реалізуємо функцію, яка визначатиме значення CA для наявної руки.
value_of_card(13)
// => 10
value_of_card(4)
// => 4
value_of_card(14)
// => 1
Визначте функцію higher_card з параметрами card_one і card_two, двома числами, кожне з яких позначає карту.
Для нарахування очок значення CJ, CQ і CK дорівнюють 10.
Функція має повертати карту з більшим значенням для нарахування очок.
Якщо обидві карти мають однакове значення, поверніть обидві.
CA може набувати кількох значень, тож у цьому завданні зафіксуємо його значення як 1.
higher_card(13, 11)
// => {13, 11}
higher_card(4, 6)
// => 6
>>> higher_card(13, 14)
// => 13
Щоб функція повернула два цілих числа, використайте обидва регістри rax і rdx:
returning_two_values:
mov rax, rdi
mov rdx, rsi
ret
Якщо повертається лише одна карта, rdx потрібно встановити в 0.
Як уже згадувалося, CA коштує або 1, або 11 очок, залежно від руки.
Правила блекджеку вимагають обирати значення CA так, щоб якомога збільшити рахунок руки, але не перевищити 21 (не «перебрати»).
Визначте функцію value_of_ace з параметрами card_one і card_two, двома числами, що позначають пару карт, які вже є в руці перед отриманням останнього CA.
Функція має повертати значення, 1 або 11, яке отримає наступний CA.
Памʼятаймо: значення нової руки (з CA) має бути якомога вищим, не перевищуючи 21.
Підказка: якщо в руці вже є CA, то значенням для наступного CA буде 1.
value_of_ace(6, 13)
// => 1
value_of_ace(7, 3)
// => 11
Якщо першими двома картами гравцеві приходять CA і десятка (C10, CJ, CQ або CK), то гравець має 21 очко.
Таку руку називають блекджеком.
Визначте функцію is_blackjack з параметрами card_one і card_two, які є двома числами, що позначають пару карт.
Функція має повертати 1, якщо рука з двох карт є блекджеком, і 0 в іншому разі.
Щоб було зручніше працювати зі значеннями, на початку файлу визначено константи TRUE і FALSE, що відповідають 1 і 0.
Примітка: рахунок можна обчислити в різні способи.
Але за можливості перевірмо, чи є в руці CA і десятка, а не підсумовуймо значення карт.
is_blackjack(14, 13)
// => 1
is_blackjack(10, 9)
// => 0
Якщо перші дві карти гравця мають однакове значення (наприклад, рука з двох C6 або рука з CQ і CK), гравець може вирішити вважати їх двома окремими руками.
Це називають «розділенням пар».
Визначте функцію can_split_pairs з параметрами card_one і card_two, які є двома числами, що позначають пару карт.
Функція має повертати 1, якщо руку з двох карт можна розділити на дві руки, і 0 в іншому разі.
Щоб було зручніше працювати зі значеннями, на початку файлу визначено константи TRUE і FALSE, що відповідають 1 і 0.
can_split_pair(12, 13)
// => 1
can_split_pair(10, 14)
// => 0
Коли дві початково роздані карти дають у сумі 9, 10 або 11 очок, гравець може зробити додаткову ставку, рівну початковій. Це називають «подвоєнням ставки».
Визначте функцію can_double_down з параметрами card_one і card_two, які є двома числами, що позначають пару карт.
Функція має повертати 1, якщо рука з двох карт дозволяє гравцеві «подвоїти ставку», і 0 в іншому разі.
Щоб було зручніше працювати зі значеннями, на початку файлу визначено константи TRUE і FALSE, що відповідають 1 і 0.
can_double_down(14, 9)
// => 1
can_double_down(10, 2)
// => 0
Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.