Модуль и s-выражения

Разбираем по косточкам синтаксис s-выражений в WAT.

Модуль — это корневой контейнер Wasm: (module ...), внутри которого живут функции, память, импорты и экспорты.

Всё начинается с module

Любой WAT-файл — это одно большое s-выражение, начинающееся с (module и заканчивающееся закрывающей скобкой. Внутри перечислены составные части модуля. Пустой модуль выглядит так — он валиден, но ничего не делает:

(module)

Что такое s-выражение

S-выражение — это узел дерева в скобках: первый элемент после скобки задаёт тип узла (ключевое слово), остальное — его содержимое или вложенные узлы. Например, (param $x i32) — это узел типа param с именем $x и типом i32. Узлы вкладываются друг в друга, образуя дерево всей программы.

(module
  (func ...)      ;; узел-функция внутри модуля
  (memory ...)    ;; узел-память внутри модуля
  (export ...))   ;; узел-экспорт внутри модуля

Имена и комментарии

Идентификаторы в WAT начинаются с символа $: $add, $x, $counter. Это удобные псевдонимы для людей — при компиляции в бинарь они превращаются в числовые индексы. Комментарии бывают двух видов: строчные начинаются с ;; и идут до конца строки, блочные оборачиваются в (; ... ;).

(module
  ;; это строчный комментарий
  (; а это
     блочный комментарий ;)
  (func $nichego))

Два стиля записи: плоский и вложенный

WAT допускает два эквивалентных способа писать тело функции. Плоский (линейный) стиль — это последовательность инструкций, как настоящий стековый байткод. Вложенный (folded) — те же инструкции, но записанные деревом в скобках, что нагляднее показывает, что во что вкладывается.

;; плоский стиль
(func $add (param $a i32) (param $b i32) (result i32)
  local.get $a
  local.get $b
  i32.add)

;; вложенный стиль — то же самое
(func $add2 (param $a i32) (param $b i32) (result i32)
  (i32.add (local.get $a) (local.get $b)))

Оба варианта компилируются в один и тот же байткод. Вложенный читается как формула, плоский — как пошаговая работа со стеком.

Как работает под капотом

Когда wat2wasm разбирает файл, он строит из s-выражений дерево, проверяет типы и затем «разворачивает» вложенные узлы обратно в линейную последовательность стековых инструкций — ведь бинарный формат именно линейный. То есть folded-стиль это синтаксический сахар: для удобства чтения, но в байтах всё равно окажется плоская цепочка push-операций.

Частые ошибки

  • Несбалансированные скобки — самая частая ошибка в WAT. Каждой ( нужна своя ).
  • Забытый $ перед именемlocal.get add вместо local.get $add не скомпилируется.
  • Смешение стилей без понимания — можно мешать плоский и вложенный, но новичку лучше выбрать один.

Итоги

  • Корень WAT — это (module ...).
  • S-выражение — скобочный узел: ключевое слово + содержимое.
  • Имена начинаются с $, комментарии — ;; и (; ;).
  • Плоский и вложенный стили эквивалентны; folded — это сахар.
Проверьте себя
1. С чего начинается любой WAT-файл?
AС функции main
BС корневого s-выражения (module ...)
CС импорта стандартной библиотеки
DС объявления памяти
2. Что обозначает символ $ в WAT?
AГлобальную переменную
BНачало идентификатора (имени функции, параметра, локали)
CКомментарий
DКонец инструкции
3. Чем отличается folded (вложенный) стиль от плоского?
AОн быстрее исполняется
BЭто синтаксический сахар: тот же байткод, записанный деревом для наглядности
CОн поддерживает другие типы
DОн не компилируется в бинарь