Функции, параметры и локальные переменные
Учимся писать функции на WAT с параметрами и локальными переменными.
Функция Wasm — это
(func ...)с параметрами (param), результатом (result) и телом из стековых инструкций.
Анатомия функции
Объявление функции состоит из имени, входных параметров, типа результата и тела. Вот функция, возводящая число в квадрат:
(func $square (param $n i32) (result i32)
local.get $n
local.get $n
i32.mul)
Читаем по шагам: (param $n i32) объявляет вход — целое $n. (result i32) обещает вернуть целое. В теле local.get $n дважды кладёт значение параметра на стек, а i32.mul перемножает два верхних значения. То, что осталось на стеке в конце, и есть результат.
Локальные переменные
Кроме параметров, функция может объявить свои локальные переменные через (local $имя тип). Они инициализируются нулём. Записать в локаль — local.set, прочитать — local.get. Вычислим (a + b) * 2, сохранив сумму в локаль:
(func $sum_times_two (param $a i32) (param $b i32) (result i32)
(local $sum i32)
local.get $a
local.get $b
i32.add
local.set $sum ;; $sum = a + b
local.get $sum
i32.const 2
i32.mul) ;; (a + b) * 2
Параметры — это тоже локали
Важная деталь: параметры и локали живут в одном пространстве индексов. К ним обоим обращаются через local.get и local.set. Разница лишь в том, что параметры получают значения от вызывающего, а собственные локали стартуют с нуля.
Смоделируем логику на JS
Чтобы убедиться, что логика верна, повторим тот же расчёт на JavaScript — здесь видно, что именно делает Wasm-функция.
function sumTimesTwo(a, b) {
let sum = a + b; // local.set $sum
return sum * 2; // i32.mul на 2
}
console.log(sumTimesTwo(3, 4));
console.log(sumTimesTwo(10, 5));
Вывод:
14 30
Как работает под капотом
При вызове функции движок создаёт «кадр» (frame), где хранятся значения параметров и локалей. Стек операндов работает поверх этого кадра. Когда функция доходит до конца тела, движок проверяет: на стеке должно остаться ровно столько значений, сколько объявлено в result (для нашей $square — одно i32). Если форма стека не сходится, модуль не пройдёт валидацию.
Частые ошибки
- Несоответствие result и стека — обещали
(result i32), но в конце на стеке пусто или два значения. Ошибка валидации. - Путаница типов —
local.getкладёт значение объявленного типа; нельзя сложитьi32иf64. - Забыли local.get после local.set —
local.setснимает значение со стека, и оно «исчезает» из вычисления, пока вы снова не вызоветеlocal.get.
Итоги
- Функция:
(func имя (param ...) (result ...) тело). - Локали объявляются через
(local ...)и стартуют с нуля. - Параметры и локали — единое пространство, доступ через
local.get/set. - В конце на стеке должно остаться ровно столько значений, сколько в
result.