WAT (WebAssembly Text Format) adalah bentuk teks dari Wasm yang bisa dibaca manusia. wat2wasm, salah satu tool di wabt (WebAssembly Binary Toolkit), merakit teks itu jadi file .wasm berisi byte. Bayangin Bu Sri menulis pesanan tamu di buku tulis: "Kamar Teras, 2 malam". Resepsionis lalu memindahkannya ke formulir sistem pemesanan yang kotaknya berurutan dan bernomor: jenis tamu di kotak 1, daftar kamar di kotak 3, nama yang boleh dipanggil di kotak 7, rinciannya di kotak 10. Isinya sama persis, cuma bentuknya beda. Catatan di buku itu WAT, formulirnya .wasm, dan resepsionisnya wat2wasm.
🧪 Tebakan cepat: di file
harga.wasmyang 47 byte, satu byte6cdiganti6a. KalauhargaInap(2, 350000)dipanggil, hasilnya berapa? Tebak dulu, jawabannya ada di bagian "Bedah 47 byte-nya".
WAT: teks untuk mesin stack
Ini fungsi harga menginap Homestay Kabut, harga.wat:
;; harga.wat: harga menginap di Homestay Kabut
(module
(func $hargaInap (param $malam i32) (param $hargaPerMalam i32) (result i32)
local.get $malam ;; stack: [malam]
local.get $hargaPerMalam ;; stack: [malam, hargaPerMalam]
i32.mul) ;; stack: [malam × hargaPerMalam]
(export "hargaInap" (func $hargaInap)))
(module ...)membungkus semuanya. Satu file WAT = satu modul.;;memulai komentar.(func $hargaInap ...)mendefinisikan fungsi. Nama yang diawali$cuma untuk manusia; di file biner, fungsi dan parameter dikenal lewat nomor urutnya.(param $malam i32)adalah parameter bertipei32, bilangan bulat 32-bit.(result i32)berarti fungsi ini mengembalikan satui32.local.get $malammenaruh nilai parameter itu di atas stack.i32.mulmengambil dua angka teratas dari stack, mengalikannya, lalu menaruh hasilnya. Instruksi lain dengan pola sama:i32.add(tambah) dani32.sub(kurang).- Fungsi selesai di kurung tutup. Angka yang tersisa di stack jadi nilai kembaliannya.
(export "hargaInap" (func $hargaInap))membuat fungsi ini bisa dipanggil dari luar dengan nama"hargaInap". Tanpa export, JavaScript gak bisa melihatnya.
Tipe angka di Wasm cuma empat: i32, i64 (bilangan bulat 32 dan 64 bit), f32, f64 (pecahan). Instruksi selalu menyebut tipenya: i32.mul beda dengan f64.mul. Satu contoh stack lagi, (2 + 3) × 4:
| Instruksi | Stack sesudahnya |
|---|---|
i32.const 2 |
[2] |
i32.const 3 |
[2, 3] |
i32.add |
[5] |
i32.const 4 |
[5, 4] |
i32.mul |
[20] |
Merakit dengan wat2wasm
wabt dirilis di GitHub (WebAssembly/wabt) untuk macOS, Linux, dan Windows. Di sini dipakai rilis 1.0.42 (September 2026) yang diekstrak ke folder sendiri. brew install wabt dan apt install wabt juga ada, tapi versinya bisa lebih lama.
$ wat2wasm --version
1.0.42
$ wat2wasm harga.wat -o harga.wasm
$ xxd harga.wasm
00000000: 0061 736d 0100 0000 0107 0160 027f 7f01 .asm.......`....
00000010: 7f03 0201 0007 0d01 0968 6172 6761 496e .........hargaIn
00000020: 6170 0000 0a09 0107 0020 0020 016c 0b ap....... . .l.
-o harga.wasmmenentukan nama file hasilnya. Tanpa-o,wat2wasmmenulis ke nama yang sama dengan akhiran.wasm.xxdmenampilkan isi file per byte dalam heksadesimal: 47 byte. Kolom kanan menunjukkan byte yang kebetulan huruf, sepertiasmdanhargaInap.
wat2wasm juga memeriksa tipe sebelum menulis apa pun. Kalau local.get $hargaPerMalam lupa ditulis, i32.mul cuma menemukan satu angka di stack:
harga.wat:5:5: error: type mismatch in i32.mul, expected [i32, i32] but got [i32]
i32.mul) ;; stack: [malam × hargaPerMalam]
^^^^^^^
Bedah 47 byte-nya
File .wasm selalu dimulai dengan 8 byte pembuka, lalu section-section bernomor. Tiap section dimulai dengan nomornya dan ukurannya dalam byte.
| Offset | Byte | Artinya |
|---|---|---|
0x00 |
00 61 73 6d |
magic number: \0asm, tanda ini file Wasm |
0x04 |
01 00 00 00 |
versi format biner: 1 |
0x08 |
01 07 01 |
section type (1), 7 byte, berisi 1 tipe fungsi |
0x0b |
60 02 7f 7f 01 7f |
tipe fungsi (60): 2 parameter i32 i32 (7f = i32), 1 hasil i32 |
0x11 |
03 02 01 00 |
section function (3), 2 byte: 1 fungsi, memakai tipe nomor 0 |
0x15 |
07 0d 01 09 |
section export (7), 13 byte: 1 export, namanya 9 byte |
0x19 |
68 61 72 67 61 49 6e 61 70 |
hargaInap |
0x22 |
00 00 |
jenis export 00 = fungsi, fungsi nomor 0 |
0x24 |
0a 09 01 07 00 |
section code (10), 9 byte: 1 badan fungsi sepanjang 7 byte, 0 variabel lokal tambahan |
0x29 |
20 00 20 01 6c 0b |
local.get 0, local.get 1, i32.mul (6c), end (0b) |
wasm-objdump -d harga.wasm (juga dari wabt) menampilkan bagian terakhir itu:
000028 func[0] <hargaInap>:
000029: 20 00 | local.get 0
00002b: 20 01 | local.get 1
00002d: 6c | i32.mul
00002e: 0b | end
Byte 6c di offset 0x2d itulah i32.mul. Ganti jadi 6a (i32.add) dan hargaInap(2, 350000) mengembalikan 350002: Wasm sekarang menjumlahkan, bukan mengalikan. Itu jawaban tebakannya. Ganti jadi 6b (i32.sub) dan hasilnya -349998. Nama $malam dan $hargaPerMalam gak ada di 47 byte ini. Balikkan file-nya dengan wasm2wat harga.wasm, dan yang keluar local.get 0 dan local.get 1. Kalau nama itu mau ikut disimpan, rakit dengan wat2wasm --debug-names: file-nya jadi 95 byte karena ada section name.
Memuat dari JavaScript
Kalau byte-nya sudah di tanganmu (di Uint8Array, atau hasil fetch lalu arrayBuffer()), pakai WebAssembly.instantiate:
const bytes = new Uint8Array([0x00, 0x61, 0x73, 0x6d, /* ...47 byte harga.wasm... */]);
WebAssembly.instantiate(bytes).then(({ module, instance }) => {
const { hargaInap } = instance.exports;
console.log(hargaInap(2, 350000)); // 700000
});
Kalau file-nya ada di server, cara paling efisien adalah instantiateStreaming. Dia meng-compile sambil file-nya masih diunduh, lalu mengembalikan Promise berisi module dan instance:
WebAssembly.instantiateStreaming(fetch('harga.wasm'), imports).then(({ instance }) => {
const { hargaInap } = instance.exports;
console.log(hargaInap(2, 350000)); // 700000
});
- Argumen kedua (
imports) adalah objek import. Modulharga.wasmgak minta import apa pun, jadi boleh dikosongkan. - Server harus mengirim
Content-Type: application/wasm. Buka halamannya lewat server lokal, bukan dengan klik dua kali file HTML-nya (file://).
⚠️ Jebakan yang sering ketemu
- Lupa
(export ...). Modulnya tetap jalan, tapiinstance.exportskosong.const { hargaInap } = instance.exportsjadiundefined, dan memanggilnya melemparTypeError: hargaInap is not a function. - Salah urutan di stack.
i32.submenghitung angka kedua dari atas dikurangi angka teratas. Urutanlocal.getmenentukan hasilnya. - Mengira
instantiate(module)sama denganinstantiate(bytes). Dengan byte, hasilnya{ module, instance }. DenganWebAssembly.Moduleyang sudah di-compile, hasilnya langsungInstance. - Salah Content-Type di server.
instantiateStreamingmenolak file yang dikirim sebagaiapplication/octet-streamdenganTypeError.
TL;DR: WAT adalah bentuk teks Wasm yang bisa dibaca manusia: (module (func $nama (param i32) (result i32) ...) (export "nama" (func $nama))), dengan instruksi stack seperti local.get, i32.add, i32.mul. wat2wasm harga.wat -o harga.wasm (wabt 1.0.42) merakitnya jadi 47 byte: magic 00 61 73 6d, versi 01 00 00 00, lalu section type (1), function (3), export (7), dan code (10), tempat 20 00 20 01 6c 0b berarti local.get 0, local.get 1, i32.mul, end. Ganti 6c jadi 6a dan hasilnya 350002. Nama $ gak ikut tersimpan kecuali pakai --debug-names. Dari JavaScript, pakai WebAssembly.instantiate(bytes) untuk byte yang sudah ada, atau WebAssembly.instantiateStreaming(fetch("harga.wasm"), imports) untuk file dari server dengan Content-Type: application/wasm.