Обмен числами: быстрая граница

Разбираем самый простой и быстрый случай обмена — числа.

Числовая граница между JS и Wasm почти бесплатна: целые и дробные передаются напрямую как значения.

Почему числа — это легко

Когда JS вызывает instance.exports.add(2, 3), аргументы 2 и 3 кладутся прямо на стек операндов Wasm-функции, а результат возвращается тем же путём. Никакого копирования больших структур, никакой памяти — просто значения. Поэтому числовые вызовы — самая дешёвая форма взаимодействия двух миров.

Отображение типов

JS знает только один числовой тип — number (это всегда f64 внутри). Wasm же различает четыре типа. Вот как они стыкуются:

Тип WasmСо стороны JS
i32обычный number (целое в пределах 32 бит)
f32 / f64обычный number
i64BigInt (обычное 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 ↔ JS number; i64BigInt.
  • Обычный number точен лишь до 2^53 — отсюда BigInt для i64.
  • Граница дорожает на массивах и строках, не на числах.
Проверьте себя
1. Как тип i64 из Wasm отображается на сторону JavaScript?
AКак обычный number
BКак BigInt
CКак строка
DКак массив из двух чисел
2. Почему числовой обмен между JS и Wasm дешёвый?
AЧисла сжимаются
BАргументы кладутся прямо в регистры процессора, без обхода памяти и сериализации
CJS кэширует все числа
DWasm не проверяет типы чисел
3. До какого целого обычный JS number точен?
AДо 2^32
BДо 2^53
CДо 2^63
DДо бесконечности