У цій вправі ми визначимо синтаксис і модель виконання для невеликої мови, натхненної мовою асемблера x86-64.
Мета - формалізувати, як код пишеться, розбирається та виконується в обмеженому середовищі.
Код асемблера записується всередині спеціальної конструкції:
let program := assemble!(
-- assembly code here
)
Ця конструкція має створити програму, яка симулює функцію, що виконує код асемблера й повертає значення. Потім цю програму викликають за допомогою такого синтаксису, де a, b, c тощо - аргументи:
let result := program(a, b, c, ...)
Усі аргументи та повернене значення - 64-бітні цілі числа зі знаком. Дозволено щонайбільше 6 аргументів.
Повернене значення та всі аргументи передаються в регістрах. Наша мова має підтримувати такі регістри:
| регістр | роль |
|---|---|
rdi |
1-й аргумент |
rsi |
2-й аргумент |
rdx |
3-й аргумент |
rcx |
4-й аргумент |
r8 |
5-й аргумент |
r9 |
6-й аргумент |
rax |
повернене значення |
Регістри починаються з типового значення 0, якщо їх не використано для передачі аргументів у функцію.
Наприклад, у наведеній нижче програмі всі регістри мають значення 0, крім rdi, який ініціалізовано значенням 10, та rsi, який ініціалізовано значенням -20:
program(10, -20)
Повернене значення програми завжди зберігається в rax.
Назви регістрів нечутливі до регістру.
Це означає, що rax, RAX і rAx указують на той самий регістр.
Кожен рядок у коді асемблера може мати одну з двох форм:
Якщо жодна інструкція не змінює цього, виконання програми відбувається лінійно. Це означає, що попередній рядок повністю виконується, перш ніж виконається наступний.
Мітки мають такий синтаксис:
<label>:
Вони не змінюють значення жодного регістра й не впливають на програму інакше, ніж позначають конкретні місця коду, щоб їх можна було використати в інструкціях.
Мітки чутливі до регістру.
Це означає, що Start, start і sTart - це різні мітки.
Більшість інструкцій мають такий синтаксис:
<opcode> <destination>, <source>
opcode указує на операцію, яка виконується, використовуючи значення операндів destination та source. Результат операції зберігається в операнді destination.
Наприклад, інструкція add додає значення операндів destination і source та зберігає цей результат в операнді destination.
Операнд destination - завжди регістр, а операнд source може бути регістром або літеральним числом.
Ось список інструкцій, які має підтримувати наша програма:
| opcode | виконувана операція |
|---|---|
mov |
destination := source |
add |
destination := destination + source |
sub |
destination := destination - source |
mul |
destination := destination * source |
div |
destination := destination / source |
xor |
destination := destination ^^^ source |
and |
destination := destination &&& source |
or |
destination := destination ||| source |
shl |
destination := destination <<< source |
shr |
destination := destination >>> source |
Крім них, наша програма має підтримувати інструкцію cmp з двома операндами. Ця інструкція не змінює жодного регістра, а натомість порівнює операнди destination і source та встановлює внутрішній стан програми в одне з трьох значень:
destination > source
destination == source
destination < source
Як саме ми відслідковуємо цей стан усередині - на наш розсуд.
Є інструкції, які змінюють потік виконання програми, передаючи керування в інше місце коду. Їх називають інструкціями переходу.
Усі вони приймають лише один операнд - мітку, яка вказує ціль переходу, тобто місце в коді, де продовжиться виконання:
<opcode> <label>
Інструкція jmp завжди виконує перехід до цільової мітки.
Інші інструкції переходу виконують перехід лише тоді, коли внутрішній стан програми встановлено в конкретне значення. Це означає, що перед ними зазвичай стоїть інструкція cmp.
Ось інструкції переходу, які має підтримувати наша програма:
| інструкція | виконувана операція |
|---|---|
jmp |
безумовний перехід. Перехід виконується завжди |
je |
виконує перехід, якщо внутрішній стан дорівнює |
jl |
виконує перехід, якщо внутрішній стан менше |
jg |
виконує перехід, якщо внутрішній стан більше |
Коди інструкцій, незалежно від того, чи мають вони два операнди чи один, нечутливі до регістру.
Це означає, що add, ADD і aDd указують на ту саму інструкцію.