Курс ЛР8 · Місія 8
Академічна↔ Сюжет С.І.Д.СРС до ЛР⌨️ команди 6 · 3 нових

Лабораторна робота № 8. Модуль на WAT вручну

Мета

Навчитися писати мінімальний модуль WebAssembly безпосередньо у текстовому форматі .wat — без компілятора з мови вищого рівня — і запускати його в браузері: оголошувати функцію зі стековою логікою обчислень, керувати потоком виконання структурованим циклом (block/loop/br_if) та читати й писати байти в лінійній пам'яті модуля.

📚 ТЕОРЕТИЧНІ ВІДОМОСТІ

Теоретичні відомості

WebAssembly (WASM) — бінарний формат інструкцій для стекової віртуальної машини, однаково виконуваний у всіх сучасних браузерних рушіях (V8, SpiderMonkey) і поза браузером (Wasmtime, Wasmer). Це компактний, платформонезалежний проміжний код — типова ціль компіляції з C, Rust, Go тощо, який також можна писати вручну.

WebAssembly Text Format (.wat) — людиночитне текстове представлення того самого байткоду у вигляді S-виразів (як у Lisp: усе вкладене в дужки). .wat-файл компілюється в бінарний .wasm утилітою wat2wasm з пакета WABT (WebAssembly Binary Toolkit); зворотне перетворення виконує wasm2wat.

Модель обчислень. На відміну від x86-64, де інструкції оперують іменованими регістрами, WASM використовує операційний стек значень чотирьох числових типів: i32, i64, f32, f64.

Інструкція Дія зі стеком
i32.const N кладе константу N на вершину стека
i32.add / i32.sub / i32.mul / i32.rem_s знімає два верхні значення, кладе результат
local.get $x кладе значення локальної змінної/параметра $x на стек
local.set $x знімає верхнє значення стека й записує в $x

Параметри функції та секції (local ...) — це саме такі іменовані комірки, доступні виключно через local.get/local.set; прямого «регістрового» доступу до них немає.

Керування потоком у WASM структуроване — навмисно немає інструкції, аналогічної безумовному jmp асемблера на довільну адресу. Замість цього:

  • block $мітка ... end — іменована ділянка; br $мітка всередині неї здійснює вихід одразу після відповідного end.
  • loop $мітка ... end — іменована ділянка; br $мітка всередині неї здійснює перехід на початок тіла (повторення ітерації).
  • br_if $мітка — умовний варіант br: знімає i32 зі стека, виконує перехід лише якщо значення ненульове.

Стандартна ідіома циклу з умовою виходу — loop, обгорнутий у block: br_if на зовнішній block працює як break, а br наприкінці loop — як перехід до наступної ітерації.

Лінійна пам'ять — окрема від стека та локальних змінних ділянка: суцільний масив байтів, адресований i32, що росте сторінками по 64 КіБ. (memory 1) виділяє одну сторінку. Доступ — інструкціями i32.store/i32.load (4 байти) або i32.store8/i32.load8_u (1 байт). Модель безпеки WASM гарантує, що доступ поза межами виділеної пам'яті завершується контрольованим trap, а не непередбачуваною поведінкою — модуль фізично не здатний адресувати пам'ять хоста.

Обладнання та програмне забезпечення

Порядок виконання

  1. Відкрити середовище розробника, переконатися, що wat2wasm --version виконується без помилок.
  2. Створити файл module.wat за специфікацією у starter/README.md і заповнити тіло функції рівня 1. Очікуваний результат: файл із функцією $compute, готовий до компіляції.
  3. Скомпілювати: wat2wasm module.wat -o module.wasm. Очікуваний результат: без помилок валідації, з'являється файл module.wasm.
  4. Підняти локальний сервер (python3 -m http.server 8080) і відкрити index.html. Очікуваний результат: сторінка виводить у <pre id="log"> числовий результат виклику compute(2, 3), а не помилку.
  5. У консолі DevTools викликати експортовану функцію (window.mod.compute(...)) на кількох парах значень і перевірити коректність результату за операцією свого варіанта.
  6. Виконати рівень 2 (цикл $sum_range) і рівень 3 (лінійна пам'ять, $write_byte, $read_byte) за тим самим файлом module.wat, дотримуючись специфікації автотестів і завдання рівнів.
  7. Запустити локальний скрипт перевірки з autograder/, переконатися, що всі перевірки рівня 1 проходять, перш ніж здавати роботу.
РІВЕНЬ 1 · «3» · ПЕРШОКУРСНИК

Рівень 1 (оцінка 3)

Написати функцію $compute (param $a i32) (param $b i32) (result i32) у текстовому форматі WebAssembly вручну — послідовність local.get/i32.const/арифметичної інструкції (i32.add, i32.mul або i32.rem_s — залежно від варіанта, призначеного викладачем), без заглушки unreachable, і продемонструвати виклик у браузері.

РІВЕНЬ 2 · «4» · МАГІСТР

Рівень 2 (оцінка 4, захист)

Додати до того самого module.wat функцію $sum_range (param $n i32) (result i32), яка підсумовує цілі числа від 0 до n - 1 за допомогою block/loop/br_if. На захисті — усно пояснити семантику br у block проти br у loop та показати виклик функції в DevTools.

РІВЕНЬ 3 · «5» · ЛЕГЕНДА

Рівень 3 (оцінка 5, розширення)

Оголосити (memory 1) і реалізувати функції $write_byte (param $addr i32) (param $value i32) (запис через i32.store8) та $read_byte (param $addr i32) (result i32) (читання через i32.load8_u). На захисті продемонструвати запис і зчитування байта за адресою та пояснити поведінку модуля при зверненні за межі виділеної пам'яті.

🧪 РОЗІБРАНИЙ ПРИКЛАД

Розібраний приклад

Мінімальний модуль з однією функцією (шаблон для рівня 1, тут — з операцією додавання):

(module
  (func $compute (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add)

  (export "compute" (func $compute)))

Компіляція та автономна перевірка без браузера:

wat2wasm module.wat -o module.wasm
node -e "
  const fs = require('fs');
  WebAssembly.instantiate(fs.readFileSync('module.wasm')).then(({instance}) => {
    console.log(instance.exports.compute(2, 3));
  });
"

Цикл рівня 2, що підсумовує 0..n-1:

(func $sum_range (param $n i32) (result i32)
  (local $i i32)
  (local $acc i32)
  (block $break
    (loop $continue
      (br_if $break (i32.ge_s (local.get $i) (local.get $n)))
      (local.set $acc (i32.add (local.get $acc) (local.get $i)))
      (local.set $i (i32.add (local.get $i) (i32.const 1)))
      (br $continue)))
  (local.get $acc))

Лінійна пам'ять рівня 3:

(memory (export "memory") 1)

(func $write_byte (param $addr i32) (param $value i32)
  local.get $addr
  local.get $value
  i32.store8)

(func $read_byte (param $addr i32) (result i32)
  local.get $addr
  i32.load8_u)
❓ КОНТРОЛЬНІ ПИТАННЯ

Контрольні питання

  1. Чим стекова модель обчислень WASM принципово відрізняється від регістрової моделі x86-64?
  2. Що робить інструкція local.get, а що — local.set?
  3. Чому в WASM немає інструкції, аналогічної безумовному jmp на довільну адресу?
  4. Яка різниця між block і loop з точки зору того, куди веде br всередині них?
  5. Що станеться, якщо спробувати виконати i32.add, коли на стеці лежить лише одне значення?
  6. Що таке лінійна пам'ять і чим вона відрізняється від локальних змінних функції?
  7. Яку роль відіграє wat2wasm у цьому робочому процесі?
  8. Чому WebAssembly-модуль не можна відкрити напряму через file:// в браузері?

Парні теми СРС

Критерії оцінювання та форма звіту

Здається гілка/тег submit/lab08 з файлом module.wat (та module.wasm, зібраним з нього) і мінімальною index.html, що завантажує модуль. Автотести рівня 1 (див. autograder/README.md) перевіряють: файл компілюється без помилок wat2wasm, експортована функція має правильну сигнатуру (кількість і типи параметрів, тип результату), повертає коректні значення на наборі тестових входів (типові додатні значення, нуль, від'ємні значення, межові значення близько i32::MAX/i32::MIN). Рівень 2 і 3 здаються тим самим файлом з доданими секціями коду і перевіряються вручну на захисті: студент показує виклик у DevTools і пояснює кожен рядок доданого .wat-коду (яка інструкція що кладе/знімає зі стека, для чого саме i32.store/i32.load у рівні 3). Звіт додає скріншоти: вихідний код .wat, вивід компіляції, консоль браузера з результатом виклику.

⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 6 · 3 нових

Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.

wat2wasm
З WABT: перекладає текстовий формат WAT у бінарний модуль .wasm (wat2wasm m.wat -o m.wasm).
nodeнове
Рантайм JavaScript; node -e "..." виконує код прямо з рядка — так ми запускаємо .wasm без браузера.
wasm2wat
З WABT: зворотне — бінарний .wasm назад у читабельний WAT.
wasm-objdumpнове
З WABT: дизасемблер WebAssembly — -d показує опкоди й стекові інструкції, -x — секції та експорти.
npmнове
Менеджер пакетів Node.js (npm i -g wabt — встановити інструменти WABT глобально).
python3
Інтерпретатор Python; тут — python3 -m http.server 80, найкоротший спосіб підняти тестовий HTTP-сервер.

💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.