Треки
/
Lean
Lean
/
Вправи
/
Асемблювати
Асемблювати

Асемблювати

Складна

Вказівки

У цій вправі ми визначимо синтаксис і модель виконання для невеликої мови, натхненної мовою асемблера 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.

Note

Назви регістрів нечутливі до регістру. Це означає, що rax, RAX і rAx указують на той самий регістр.

Код асемблера

Кожен рядок у коді асемблера може мати одну з двох форм:

  • Мітки позначають конкретні місця програми, які використовують деякі інструкції.
  • Інструкції описують операції, які виконує програма.

Якщо жодна інструкція не змінює цього, виконання програми відбувається лінійно. Це означає, що попередній рядок повністю виконується, перш ніж виконається наступний.

Мітки

Мітки мають такий синтаксис:

<label>:

Вони не змінюють значення жодного регістра й не впливають на програму інакше, ніж позначають конкретні місця коду, щоб їх можна було використати в інструкціях.

Note

Мітки чутливі до регістру. Це означає, що 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 виконує перехід, якщо внутрішній стан більше
Note

Коди інструкцій, незалежно від того, чи мають вони два операнди чи один, нечутливі до регістру. Це означає, що add, ADD і aDd указують на ту саму інструкцію.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Lean Exercism

Час розпочати Асемблювати?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Lean, а також 100 вправ та справжнє наставництво від людей, і все це безкоштовно.