Обмен числами: быстрая граница
Разбираем самый простой и быстрый случай обмена — числа.
Числовая граница между JS и Wasm почти бесплатна: целые и дробные передаются напрямую как значения.
Почему числа — это легко
Когда JS вызывает instance.exports.add(2, 3), аргументы 2 и 3 кладутся прямо на стек операндов Wasm-функции, а результат возвращается тем же путём. Никакого копирования больших структур, никакой памяти — просто значения. Поэтому числовые вызовы — самая дешёвая форма взаимодействия двух миров.
Отображение типов
JS знает только один числовой тип — number (это всегда f64 внутри). Wasm же различает четыре типа. Вот как они стыкуются:
| Тип Wasm | Со стороны JS |
i32 | обычный number (целое в пределах 32 бит) |
f32 / f64 | обычный number |
i64 | BigInt (обычное number не вмещает 64 бита точно) |
Подвох с i64
Число JS (f64) точно представляет целые лишь до 2^53. А i64 Wasm хранит до 2^63. Чтобы не терять точность, при пересечении границы i64 отображается на BigInt. Покажем, почему обычный number здесь подвёл бы:
const big = 9007199254740993n; // BigInt: 2^53 + 1
console.log("BigInt точно:", big.toString());
console.log("как number:", Number(big)); // потеря точности!
Вывод:
BigInt точно: 9007199254740993 как number: 9007199254740992
Видно: переведя большое целое в обычный number, мы потеряли единицу. Поэтому i64 и работает через BigInt — вызывая такую функцию, аргумент передают как 5n, а не 5.
Округление f32
Ещё тонкость: f32 (32-битный float) менее точен, чем f64. Передавая в Wasm-функцию с параметром f32 дробное число, вы можете получить его слегка округлённую версию — это нормальное поведение одинарной точности, не баг.
Как работает под капотом
На уровне машинного кода числовой аргумент просто кладётся в регистр процессора перед вызовом функции, а результат читается из регистра после возврата. Именно поэтому накладные расходы минимальны — нет ни сериализации, ни обхода памяти. Граница «дорожает» только тогда, когда данные не помещаются в регистры: массивы, строки, объекты. Об этом — следующий урок.
Частые ошибки
- Передать
i64как обычное число — нужноBigInt(5n), иначе TypeError. - Ждать точности больше 2^53 от
number— для больших целых используйтеi64/BigInt. - Удивляться округлению
f32— одинарная точность по природе менее точна, чемf64.
Итоги
- Числа передаются через границу напрямую и очень быстро.
i32,f32,f64↔ JSnumber;i64↔BigInt.- Обычный
numberточен лишь до 2^53 — отсюда BigInt для i64. - Граница дорожает на массивах и строках, не на числах.