Курс СРС SRS15
до ЛР8 рівень 2 → «4»↔ Лабораторна 8↔ Лекція 8⌨️ команди 4 · 1 нова
Самостійна робота · ~2.5 год

Стекова машина WebAssembly і структура текстового формату .wat

🌌 Асемблер нейроімпланта — С.І.Д. знайомить ранера зі Стосом — небалакучим мешканцем WebAssembly-кварталу, що тримає операційний стек і обслуговує кожну інструкцію лише з вершини стопки. Перш ніж підкидати нейроімплант охороні, треба відчути цю стійку до останнього байта: одна переплутана інструкція чи неврахований залишок на стеці — і Стос не випустить модуль, а антивірус монітора помітить чужий почерк.

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

WebAssembly як стекова віртуальна машина

На відміну від x86-64 чи ARM (регістрових архітектур, де операнди зберігаються в іменованих регістрах, а інструкція явно вказує, з якими регістрами працює), WebAssembly визначає стекову машину (stack machine) — модель, у якій усі значення лежать на спільній «стопці» (стеку): кожна інструкція бере операнди виключно з вершини операційного стека і кладе результат туди ж. Інструкція не має власного «імені регістру», з яким працює, — весь потік даних неявно проходить через стек. Це не деталь реалізації, а частина формальної специфікації: валідатор WASM статично перевіряє, що для кожної точки виконання типи значень на стеці відповідають тому, чого очікує наступна інструкція — саме тому WASM можна перевірити на коректність типів ще до виконання, за лінійний прохід по байткоду, без символьного виконання.

Текстовий формат .wat (WebAssembly Text Format) — людяне, S-виразне (у стилі Lisp) представлення того самого байткоду, що компілюється в бінарний .wasm інструментом wat2wasm (пакет WABT — WebAssembly Binary Toolkit) і назад декомпілюється через wasm2wat. Модуль оголошується як (module ...), а функція — як (func $ім'я (param $x i32) (result i32) ...) з тілом із послідовності інструкцій. Приклад обчислення (2 + 3) * 4:

(module
  (func $calc (result i32)
    i32.const 2
    i32.const 3
    i32.add       ;; стек: [5]
    i32.const 4
    i32.mul))     ;; стек: [20]
  (export "calc" (func $calc)))

Простежмо стан стека покроково: i32.const 2 кладе значення 2 (стек: [2]); i32.const 3 кладе 3 (стек: [2, 3]); i32.add знімає два верхні значення (3 і 2), додає їх і кладе результат назад (стек: [5]); i32.const 4 кладе 4 (стек: [5, 4]); i32.mul знову знімає два верхні значення (4 і 5), перемножує й кладе результат (стек: [20]). Функція з (result i32) повинна залишити на стеці рівно одне значення відповідного типу в момент виходу — валідатор відхилить модуль, якщо на стеці лишиться забагато значень, замало, або невідповідного типу.

WASM визначає чотири базові числові типи: i32, i64 (цілі, без розрізнення знаковості на рівні типу — знаковість визначає сама інструкція, наприклад i32.div_s проти i32.div_u), f32, f64 (з плаваючою точкою за IEC 60559). Кожна арифметична інструкція строго типізована суфіксом (i32.add працює лише з парою i32 на стеці) — спроба виконати i32.add, коли на стеці лежить f64, провалить валідацію модуля ще до завантаження в рушій, а не викличе помилку рантайму.

Керування потоком виконання (умовні переходи, цикли) у WASM теж побудоване навколо структурованих блоків (block, loop, if/else) зі своєю власною «сигнатурою стека» для кожного блоку — на відміну від довільних goto/jmp у традиційному асемблері, WASM свідомо забороняє довільні переходи всередину/назовні довільних точок коду, що спрощує і статичну верифікацію, і компіляцію в машинний код браузерним JIT-компілятором.

📖 ОПРАЦЮВАТИ

Прочитати розділи «Structure» і «Execution — Stack» WebAssembly Core Specification (W3C); написати вручну три рядки .wat, що обчислюють (2 + 3) * 4 через i32.const/i32.add/i32.mul, і перевірити результат через wat2wasm та wasm-interp з пакета WABT.

🖥️ ТЕРМІНАЛ — СПРОБУЙ САМ
🎯 ЗАВДАННЯ
  1. Написати вручну модуль .wat, що обчислює (7 - 2) * (1 + 3) через явну послідовність i32.const/i32.sub/i32.add/i32.mul, скомпілювати через wat2wasm і виконати через wasm-interp --run-all-exports, звірившись з очікуваним результатом 20.
  2. Намалювати (у вигляді тексту чи таблиці) покроковий стан стека для кожної інструкції написаного модуля — які значення лежать на стеці до і після кожного рядка.
  3. Навмисно переставити місцями дві інструкції (наприклад, поставити i32.mul перед i32.add у прикладі з (2+3)*4) і зафіксувати, яке хибне числове значення це дає, пояснивши це через порядок значень на стеці.
  4. Написати функцію з параметром (param $x i32) (result i32), що обчислює x*x + 1, використовуючи local.get $x двічі для дублювання значення на стеці, і перевірити через кілька викликів з різними аргументами.
  5. Навмисно залишити на стеці зайве значення в кінці функції (наприклад, додати зайвий i32.const після обчислення результату) і зафіксувати точний текст помилки валідації wat2wasm про невідповідність типу результату функції.
❓ КОНТРОЛЬНІ ПИТАННЯ
  1. Чим стекова архітектура WebAssembly принципово відрізняється від регістрової архітектури x86-64 з погляду того, звідки інструкція бере операнди?
  2. Що лежить на стеці після i32.const 2; i32.const 3; i32.add; i32.const 4, і скільки значень зніме наступний i32.mul?
  3. Які чотири базові числові типи визначає специфікація WebAssembly, і як позначається знаковість у арифметичних інструкціях над цілими?
  4. Що перевіряє валідатор WASM щодо стека для функції з оголошеним (result i32) перед завершенням її виконання?
  5. Чим відрізняються інструменти wat2wasm і wasm2wat за напрямком перетворення?
  6. Чому структуровані блоки (block/loop/if) у WASM вважаються безпечнішими за довільні goto традиційного асемблера з погляду статичної верифікації?
  7. Що станеться при спробі виконати i32.add, коли на вершині стека лежить значення типу f64?
✅ САМОПЕРЕВІРКА

Що лежить на стеці після виконання i32.const 2; i32.const 3; i32.add; i32.const 4 — і скільки значень зніме з нього наступна інструкція i32.mul?

🥚 ПАСХАЛКА
Поклади на стек i32.const 3101 і подивись на модуль через wasm-objdump -d: Стос запише число трьома байтами — 41 9d 18, LEB128-кодування 0xC1D. С.І.Д. каже, що це перше число, яке творці змусили його порахувати вручну, — і що кіт тоді сидів на клавіатурі.
⌨️ КОМАНДИ НА ЦІЙ СТОРІНЦІ 4 · 1 нова

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

cat
Виводить вміст файлу в термінал повністю (від concatenate); зручно для коротких файлів і /proc/....
wat2wasm
З WABT: перекладає текстовий формат WAT у бінарний модуль .wasm (wat2wasm m.wat -o m.wasm).
wasm-interpнове
З WABT: виконує модуль .wasm без браузера (--run-all-exports — викликати всі експортовані функції).
wasm2wat
З WABT: зворотне — бінарний .wasm назад у читабельний WAT.

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

← SRS14усі темиSRS16 →