Memori Wasm (linear memory) adalah satu deretan byte panjang yang dibaca dan ditulis lewat alamat: byte ke-0, ke-1, dan seterusnya. Dia cuma bisa bertambah panjang, per 65.536 byte, dan gak ada garbage collector yang membersihkannya. Siapa yang boleh memakai bagian mana dicatat oleh allocator. Bayangin gudang Homestay Kabut: satu rak panjang dengan slot bernomor. Raknya bisa disambung per bagian, tapi gak pernah dipotong lagi. Bu Sri mencatat di buku slot mana yang dipakai tamu siapa. Kalau barang tamu gak pernah diambil dan catatannya gak dicoret, slotnya terpakai selamanya. Dan denah gudang lama gak berlaku lagi setiap kali rak disambung. Raknya linear memory, satu bagiannya satu halaman (page), buku catatannya allocator, barang yang gak diambil itu memory leak, dan denah lama itu view JavaScript yang jadi detached.
🧪 Tebakan cepat: foto Kamar Teras 1200 × 800 RGBA butuh 3.840.000 byte. Satu halaman memori Wasm 65.536 byte. Butuh berapa halaman: 58, 59, atau 60? Tebak dulu, jawabannya ada di bagian "Halaman 64 KiB dan memory.grow".
Linear memory: satu deretan byte
Pelajaran ini memakai memori.wat (269 byte setelah wat2wasm 1.0.42). Memorinya dideklarasikan begini:
(memory (export "memory") 1 256) ;; mulai 1 halaman (64 KiB), paling banyak 256 (16 MiB)
Dari JavaScript, memorinya terlihat sebagai satu ArrayBuffer, memory.buffer. Untuk membaca dan menulisnya, buat view:
const { memory } = instance.exports;
const bytes = new Uint8Array(memory.buffer); // per byte
const ints = new Int32Array(memory.buffer); // per 4 byte, di atas byte yang sama
ints[0] = 1000;
console.log(bytes[0], bytes[1], bytes[2], bytes[3]); // 232 3 0 0
Uint8Array,Int32Array,Float64Arraycuma cara membaca byte yang sama. 1000 =e8 03 00 00: byte terkecil disimpan duluan (little-endian), dan Wasm selalu memakai urutan itu.- Dari WAT,
i32.load8_udani32.store8membaca dan menulis satu byte di alamat yang ada di stack. Alamat di luar memori bukan undefined: Wasm berhenti denganRuntimeError: memory access out of bounds. - Gak ada objek, gak ada tipe di dalam memori. Cuma byte, dan alamatnya angka
i32biasa.
Halaman 64 KiB dan memory.grow
Memori Wasm diukur per halaman: 1 halaman = 65.536 byte (64 KiB). Foto Kamar Teras 3.840.000 byte, dibagi 65.536 = 58,59. Halaman gak bisa dipotong, jadi dibulatkan ke atas: 59 halaman (3.866.624 byte). Itu jawaban tebakannya.
- Dari Wasm,
memory.growmenambah halaman dan mengembalikan ukuran lama, atau-1kalau melewati batas. - Dari JavaScript,
memory.grow(n)juga mengembalikan ukuran lama. Kalau melewatimaximum, dia melemparRangeError: WebAssembly.Memory.grow(): Maximum memory size exceeded. - Memori gak pernah mengecil. Gak ada
memory.shrink. Sekali 235 halaman, tetap 235 halaman sampai instance-nya dibuang.
View lama jadi detached
Setiap kali memori tumbuh, memory.buffer diganti ArrayBuffer baru, dan yang lama di-detach: panjangnya jadi 0. View yang dibuat sebelumnya ikut kosong.
let lihat = new Uint8Array(memory.buffer); // 65.536 byte
const ptr = pesan(3840000); // di dalam Wasm memori tumbuh 1 -> 59 halaman
console.log(lihat.byteLength, lihat[0]); // 0 undefined
lihat.set(foto, ptr); // TypeError
lihat = new Uint8Array(memory.buffer); // view baru, 3.866.624 byte
lihat.set(foto, ptr); // berhasil
- Membaca view lama gak error, cuma
undefined. Menulis lewatsetataufillyang error. Chromium 147:TypeError: Cannot perform %TypedArray%.prototype.set on a detached or out-of-bounds ArrayBuffer. WebKit 26.4:TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds. - Bahkan
memory.grow(0)men-detach buffer lama. - Aturannya: buat view baru setelah setiap panggilan yang mungkin memesan memori, atau cek
lihat.byteLength === 0dulu. Glue wasm-bindgen melakukan persis itu sebelum menyalin string.
Bump allocator buatan tangan
Allocator paling sederhana menyimpan satu angka: alamat pertama yang belum dipakai. Memesan berarti memberi alamat itu, lalu menggesernya maju. Ini intinya di memori.wat:
(global $atas (mut i32) (i32.const 1024)) ;; 1.024 byte pertama dibiarkan kosong
(func (export "pesan") (param $n i32) (result i32)
(local $ptr i32) (local $akhir i32)
global.get $atas
local.tee $ptr ;; alamat yang akan diberikan
local.get $n
i32.add
local.set $akhir ;; $ptr + $n
local.get $akhir
memory.size
i32.const 16
i32.shl ;; ukuran memori dalam byte: halaman × 65.536
i32.gt_u
if ;; gak cukup? tambah halaman, dibulatkan ke atas
local.get $akhir
memory.size
i32.const 16
i32.shl
i32.sub
i32.const 65535
i32.add
i32.const 16
i32.shr_u
memory.grow
i32.const -1
i32.eq
if
i32.const 0 ;; melewati 256 halaman: kembalikan 0
return
end
end
local.get $akhir
global.set $atas
local.get $ptr)
(func (export "kosongkan") ;; lepas semuanya sekaligus
i32.const 1024
global.set $atas)
Lima kali pesan(3840000), lalu kosongkan(), hasilnya di browser:
| Panggilan | Hasil | $atas sesudahnya |
Halaman |
|---|---|---|---|
pesan ke-1 |
1024 | 3.841.024 | 59 |
pesan ke-2 |
3841024 | 7.681.024 | 118 |
pesan ke-3 |
7681024 | 11.521.024 | 176 |
pesan ke-4 |
11521024 | 15.361.024 | 235 |
pesan ke-5 |
0 (gagal) | 15.361.024 | 235 |
kosongkan() |
1.024 | 235 |
- Empat foto muat. Foto kelima butuh 293 halaman, melewati batas 256, jadi
memory.growmemberi-1danpesanmengembalikan 0. Selalu cek hasilnya: menulis ke alamat 0 berarti menimpa data lain. - Bump allocator gak bisa melepas satu pesanan.
kosongkan()melepas semuanya sekaligus. Cocok untuk pekerjaan per batch: proses foto, kosongkan, ulangi. - Setelah
kosongkan(), memori tetap 235 halaman. Yang balik cuma catatannya.
malloc dan free di C, allocator di Rust
Allocator sungguhan menyimpan daftar blok kosong, jadi blok yang dilepas bisa dipakai lagi.
- C dengan Emscripten:
const ptr = M._malloc(3840000), tulis lewatM.HEAPU8, laluM._free(ptr)setelah selesai. Lupa_free= leak. Buildemccdi pelajaran sebelumnya mulai dengan 258 halaman dan, tanpa-sALLOW_MEMORY_GROWTH, gak bisa tumbuh: saat heap habis, glue-nya berhenti denganAborted(OOM). - Rust: allocator bawaan untuk
wasm32-unknown-unknownadalah dlmalloc.VecdanStringdilepas otomatis saat pemiliknya selesai (ownership). Jangan pakaiwee_alloc: sejak September 2022 ditandai gak dirawat (RUSTSEC-2022-0054) dan punya issue memory leak yang gak diperbaiki. - Objek Rust yang dipegang JS:
#[wasm_bindgen] pub struct Fotojadi class JS dengan method.free(). Diukur di Node 22 dengan wasm-bindgen 0.2.129: memori mulai 17 halaman. Sepuluhnew Foto(1200, 800)tanpa.free()membuatnya 607 halaman. Sepuluh lagi dengan.free()cuma menambah jadi 666, karena blok yang dilepas dipakai ulang. Glue-nya juga mendaftarkan tiap objek diFinalizationRegistry, tapi kapan GC JavaScript jalan gak bisa ditebak, jadi jangan diandalkan. Method dipanggil setelah.free():Error: null pointer passed to rust.
Batas memori
wasm32memakai alamat 32-bit, jadi paling banyak 65.536 halaman = 4 GiB. Di Chromium 147,new WebAssembly.Memory({ initial: 1, maximum: 65537 })ditolak:RangeError: WebAssembly.Memory(): Property 'maximum': value 65537 is above the upper bound 65536. Apakah 4 GiB benar-benar didapat tergantung perangkat; di HP, alokasi sering gagal jauh sebelum itu.maximumyang kamu tulis sendiri (256 dimemori.wat) membatasi lebih awal, dan itu bagus: modul yang bocor berhenti di 16 MiB, bukan memakan seluruh RAM.
📌 Memory64
Memory64 memakai alamat i64, jadi bisa lebih dari 4 GiB. Fiturnya sudah masuk standar (Wasm 3.0) dan jalan di Chrome 133, Firefox 134, dan Node 24. Safari baru punya di balik flag: WebKit 26.4 yang dipakai di sini menolak modulnya dengan CompileError: WebAssembly.Module doesn't parse at byte 24: Memory64 is not enabled. Di Chromium 147, memori 64-bit dibatasi 262.144 halaman (16 GiB). Alamatnya i64, jadi dari JavaScript dikirim sebagai BigInt. Pakai hanya kalau datanya memang lebih dari 4 GiB.
⚠️ Jebakan yang sering ketemu
- Menyimpan view terlalu lama. Setelah memori tumbuh, view lama kosong: baca jadi
undefined,setjadiTypeError. Buat ulangnew Uint8Array(memory.buffer)setelah panggilan yang bisa memesan memori. - Lupa melepas.
_malloctanpa_free, atau objek wasm-bindgen tanpa.free(), membuat memori terus tumbuh. Karena memori gak pernah mengecil, leak-nya permanen sampai halaman dimuat ulang. - Gak mengecek hasil allocator.
pesanataumallocyang gagal mengembalikan 0. Menulis ke alamat 0 merusak data lain tanpa error. - Pakai
wee_allocdari tutorial lama. Pakai allocator bawaan Rust.
TL;DR: Linear memory adalah satu ArrayBuffer (memory.buffer) yang dibaca lewat view seperti Uint8Array dan Int32Array (little-endian), diukur per halaman 65.536 byte: foto 3.840.000 byte butuh 59 halaman. memory.grow menambah halaman (Wasm memberi -1, JS melempar RangeError kalau lewat maximum), memori gak pernah mengecil, dan tiap grow men-detach view lama (byteLength 0, set jadi TypeError), jadi buat view baru. Gak ada garbage collector untuk linear memory: allocator yang mencatat. Bump allocator cuma menggeser $atas maju dan melepas semua sekaligus; malloc/free di C dan dlmalloc di Rust bisa memakai ulang blok yang dilepas. Lupa _free atau .free() = leak (10 Foto tanpa .free(): 17 jadi 607 halaman). Batas wasm32 65.536 halaman = 4 GiB; Memory64 melewatinya di Chrome 133, Firefox 134, Node 24, belum di Safari tanpa flag. Jangan pakai wee_alloc.