Mendalami bahasa teks WebAssembly (WAT): struktur (module), (func), (export), (import), tipe (param)/(result), plus instruksi local.get/set, i32.const, aritmetika stack, call, dan deklarasi (memory) sebagai bekal menulis modul Wasm yang lebih kompleks

Setelah di episode 3 kita berhasil menjalankan modul Hello World pertama — satu fungsi add yang di-export — pada episode ini kita naik satu tingkat: menguasai bahasa teks WAT secara sungguhan. WAT adalah jendela paling jujur ke dalam WebAssembly: karena ia setara satu-ke-satu dengan bytecode binary, apa pun yang kalian tulis di WAT itulah yang benar-benar dieksekusi engine.
Mengapa memahami WAT penting? Sebagian besar kode produksi ditulis dalam Rust atau C/C++ lalu dikompilasi ke Wasm — tetapi setiap kali ada bug aneh, masalah ABI, atau optimasi yang gagal, kalian akan kembali membaca output .wat dari compiler untuk mencari tahu apa yang sebenarnya terjadi. WAT adalah bahasa assembly kalian untuk dunia Wasm.
Semua WAT dibungkus dalam satu bentuk (module ...). Di dalamnya bisa ada fungsi, memory, tabel, global, imports, dan exports — masing-masing dalam bentuk S-expression (ungkapan dengan tanda kurung).
(module
(type $t (func (param i32) (result i32)))
(import "env" "log" (func $log (param i32)))
(func $f (type $t) (param $x i32) (result i32)
local.get $x
i32.const 1
i32.add
)
(memory 1)
(export "f" (func $f))
)Modul di atas memakai hampir semua komponen yang akan kita bahas: sebuah type, satu import, satu fungsi, satu memory, dan satu export. Mari bedah satu per satu.
Fungsi adalah unit eksekusi utama. Bentuknya:
(func $nama (param $a i32) (param $b i64) (result i64)
;; body: urutan instruksi
)$nama — nama lokal yang hanya berlaku di dalam modul (opsional; tanpa nama juga valid).(param $a i32) — mendeklarasikan parameter dengan nama dan tipe.(result i64) — tipe kembalian. Wasm mendukung maksimal satu tipe kembalian (tanpa multi-value).;; — komentar gaya WAT, diabaikan oleh compiler.Di dalam body fungsi, parameter dan variabel lokal diakses lewat local.get (membaca) dan local.set (menulis):
(func $ganti (param $nilai i32) (result i32)
(local $tmp i32)
local.get $nilai
local.set $tmp
local.get $tmp
i32.const 100
i32.add
)Urutannya: baca $nilai → simpan ke $tmp → baca $tmp → tambah 100 → hasil. Perhatikan bahwa semua operasi mengalir lewat stack, sesuai arsitektur yang kita bahas di episode 2.
Konstanta dimasukkan ke stack dengan i32.const, i64.const, f32.const, atau f64.const. Operator aritmetika mengambil dua nilai teratas stack:
(func $hitung (result i32)
i32.const 10 ;; stack: [10]
i32.const 20 ;; stack: [10, 20]
i32.add ;; stack: [30]
i32.const 4 ;; stack: [30, 4]
i32.mul ;; stack: [120]
)Operasi umum yang tersedia: i32.add/sub/mul/div_s/rem_s, i32.and/or/xor, i32.eq/ne/lt_s/gt_s, dan pasangannya untuk i64/f32/f64. Perhatikan akhiran _s/_u untuk signed/unsigned pada pembagian dan perbandingan — Wasm sangat eksplisit soal ini.
Fungsi dipanggil dengan instruksi call diikuti nama atau index fungsi:
(module
(func $kuadrat (param $x i32) (result i32)
local.get $x
local.get $x
i32.mul
)
(func $main (result i32)
i32.const 7
call $kuadrat
)
(export "main" (func $main))
)Evaluasi main → dorong 7 → call $kuadrat → fungsi membaca $x = 7, menghitung 7 * 7 = 49, mengembalikan 49 sebagai hasil.
Fungsi import disediakan oleh host (JavaScript atau runtime), bukan didefinisikan di dalam modul. Ini satu-satunya cara modul Wasm berinteraksi dengan dunia luar:
(module
(import "env" "log" (func $log (param i32)))
(func $halo (param $angka i32)
local.get $angka
call $log
)
(export "halo" (func $halo))
)Saat menginstansiasi, host wajib menyediakan fungsi tersebut:
const { instance } = await WebAssembly.instantiate(wasmBytes, {
env: {
log: (n) => console.log("angka:", n),
},
})
instance.exports.halo(42)Tanda tangan import harus cocok persis — tipe (param i32) di WAT harus dipenuhi oleh fungsi JS yang menerima satu argumen angka.
Export membuat fungsi (atau memory, global, table) bisa dipanggil dari host:
(module
(func $fungsiDalam (result i32) i32.const 5)
(export "keluar" (func $fungsiDalam))
)Di JS: instance.exports.keluar() mengembalikan 5. Sebuah fungsi tanpa export tidak bisa dipanggil dari luar — ini bagian dari surface area yang dikontrol ketat oleh modul.
Memory dideklarasikan dengan ukuran awal (dan opsional maksimum) dalam satuan page (1 page = 64 KB):
(module
(memory 1 4) ;; mulai 1 page, maksimum 4 page
(data (i32.const 0) "halo") ;; tulis string "halo" di offset 0
(func $ambil (param $i i32) (result i32)
local.get $i
i32.load8_u
)
(export "memory" (memory 0))
(export "ambil" (func $ambil))
)(memory 1 4) — alokasikan 64 KB awal, boleh tumbuh hingga 256 KB.(data (i32.const 0) "halo") — segment data yang mengisi memory di offset 0 saat instansiasi.i32.load8_u — membaca satu byte dari address yang ada di stack.Tip
Semua instruksi memory (load/store) memakai address dalam byte yang diletakkan di stack. Model inilah yang membuat Wasm aman: address yang melebihi ukuran memory akan memicu trap, bukan crash atau akses memory lain.
i32.div_s untuk signed, i32.div_u untuk unsigned; salah pilih menghasilkan angka yang tidak sesuai harapan.call $kuadrat dan call 0 setara (index); memakai nama jauh lebih mudah dibaca.Pada episode 4 ini, kalian telah menguasai sintaks WAT: struktur modul, fungsi dengan param/result, instruksi stack, call, import, export, dan memory.
Inti yang harus dibawa pulang:
.wasm.(param)/(result); parameter/lokal diakses via local.get/local.set._s/_u.(import) membuka akses ke host, (export) membuka akses dari host, (memory) menyediakan linear memory.Di episode 5 selanjutnya kita akan membedah memory, tables, dan modules secara mendalam — memory.grow/memory.size, linear memory dan views-nya di JavaScript (Uint8Array/DataView), segment data, serta call_indirect untuk function pointers. Sampai jumpa di episode 5!