Линейная память: единый массив байт
Знакомимся с главным хранилищем данных Wasm — линейной памятью.
Линейная память (linear memory) — это один непрерывный массив байт, который Wasm-модуль использует для всех данных сложнее простых чисел.
Зачем отдельная память
Стек операндов годится для арифметики, но где хранить массив из миллиона чисел или текст? Для этого у модуля есть линейная память — большой плоский массив байт, адресуемый от нуля. Это как чистый лист оперативной памяти, выданный модулю. Объявляется память так:
(module
(memory 1) ;; 1 страница = 64 КБ
(export "memory" (memory 0)))
Единица измерения — страница, ровно 64 КБ (65536 байт). (memory 1) выделяет одну страницу. Память можно нарастить во время работы инструкцией memory.grow.
Чтение и запись
В память пишут инструкцией store, читают — load. Адрес — это просто число (смещение в байтах от начала). Запишем число 99 по адресу 0 и прочитаем обратно:
(func $demo (result i32)
i32.const 0 ;; адрес
i32.const 99 ;; значение
i32.store ;; память[0..3] = 99
i32.const 0 ;; адрес снова
i32.load) ;; читаем -> 99
Поскольку i32 занимает 4 байта, число по адресу 0 ляжет в байты 0–3, следующее целое логично класть по адресу 4, потом 8 и так далее.
Память общая для модуля и хоста
Ключевая идея: эту же память видит и JavaScript. Когда память экспортирована, JS получает к ней доступ как к ArrayBuffer и может читать и писать те же байты. Смоделируем эту картину на JS, используя ArrayBuffer и DataView — так работают и реальные Wasm-store/load:
const memory = new ArrayBuffer(16); // как линейная память Wasm
const view = new DataView(memory);
view.setInt32(0, 99, true); // i32.store по адресу 0 (little-endian)
view.setInt32(4, 42, true); // i32.store по адресу 4
console.log("по адресу 0:", view.getInt32(0, true));
console.log("по адресу 4:", view.getInt32(4, true));
Вывод:
по адресу 0: 99 по адресу 4: 42
Как работает под капотом
Линейная память — это, по сути, выделенный движком ArrayBuffer. Wasm-инструкции load/store компилируются в обычные обращения к этому буферу по смещению. Числа хранятся в порядке little-endian (младший байт первым). Каждое обращение проверяется на выход за границу: если адрес больше размера памяти, исполнение прерывается ловушкой (trap) — выйти за пределы своего буфера в чужую память нельзя.
Частые ошибки
- Перепутали байты и элементы — соседнее
i32лежит по адресу+4, а не+1. - Выход за границу — запись по адресу больше размера памяти вызывает trap.
- Забыли про little-endian — при ручном разборе байтов важен порядок.
Итоги
- Линейная память — непрерывный массив байт, адресуется от нуля.
- Единица — страница 64 КБ; растёт через
memory.grow. storeпишет,loadчитает; адрес — смещение в байтах.- Экспортированную память видит и JS как
ArrayBuffer.