Wasm Hello World (wat2wasm) — WebAssembly

Wasm Hello World dengan WAT WAT (WebAssembly Text Format) adalah representasi teks yang human-readable dari Wasm binary. Memahami WAT membantu kamu memahami…

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.wasm yang 47 byte, satu byte 6c diganti 6a. Kalau hargaInap(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)))

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.

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
});

⚠️ Jebakan yang sering ketemu

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.

Yang akan kamu pelajari