Wasm Component Model — WebAssembly

Wasm Component Model Component Model adalah evolusi WebAssembly yang memungkinkan modul dari berbagai bahasa saling berkomunikasi melalui high-level interface…

Component Model adalah standar yang membungkus modul Wasm jadi komponen: modul yang menulis dengan jelas apa yang ia butuhkan dan apa yang ia berikan, dengan tipe data kaya seperti list<u8>, string, record, dan result, bukan cuma angka. Bayangin Bu Sri memesan jasa olah foto dari tukang yang berbeda-beda: satu dari Wonosobo, satu dari Jogja, satu dari Bandung. Dulu tiap tukang punya cara sendiri menerima pesanan: ada yang minta nomor rak dan jumlah foto, ada yang minta fotonya ditaruh di kotak tertentu. Tiap pasangan butuh kesepakatan khusus. Sekarang ada formulir pesanan standar yang menulis apa yang diserahkan dan apa yang kembali, plus aturan baku cara mengemas barangnya. Tukang mana pun yang bisa membaca formulir itu langsung bisa bekerja. Formulirnya adalah WIT, aturan mengemasnya canonical ABI, dan tukang-tukangnya komponen yang ditulis di Rust, Go, C, atau JavaScript.

🧪 Tebakan cepat: di WIT tertulis grayscale: func(piksel: list<u8>) -> list<u8>. Fungsi core Wasm di dalam komponennya menerima apa: daftar byte utuh, dua angka i32, atau satu objek? Tebak dulu, jawabannya ada di bagian "Canonical ABI: dari list ke angka".

Core module dan komponen

Semua .wasm di pelajaran sebelumnya adalah core module. Di perbatasannya cuma angka yang lewat: i32, i64, f32, f64, plus referensi. Teks dan foto harus ditulis ke linear memory lalu dikirim sebagai pointer dan panjang, dan tiap pasangan bahasa butuh glue code sendiri (wasm-bindgen untuk Rust dan JS, syscall/js untuk Go). Kalau modul Rust mau dipakai modul Go, kedua sisi harus sepakat manual soal tata letak memorinya. Ide untuk menyelesaikan ini dulu bernama interface types; sekarang jadi bagian dari Component Model.

Core module Komponen
Yang lewat perbatasan i32, i64, f32, f64, referensi juga string, list, record, variant, option, result, resource
Memori bisa di-export dan dibagi tersembunyi di dalam komponen, gak dibagi
Kontrak daftar fungsi dengan tipe angka WIT: interface dan world
Byte ke-5 sampai ke-8 01 00 00 00 0d 00 01 00

Komponen adalah bungkus: di dalamnya ada satu atau lebih core module, ditambah tipe yang ditulis di WIT dan instruksi untuk mengubah tipe kaya jadi angka. Browser dan Node cuma mengenal core module. Chromium 147 menolak komponen dengan CompileError: WebAssembly.compile(): expected version 01 00 00 00, found 0d 00 01 00 @+4, WebKit 26.4 dengan CompileError: WebAssembly.Module doesn't parse at byte 0: unexpected version number 65549 expected 1.

WIT: kontrak tool foto Bu Sri

WIT (Wasm Interface Type) adalah bahasa untuk menulis kontraknya. Ini wit/olahfoto.wit:

package homestay:[email protected];

/// Foto kamar Homestay Kabut: RGBA, 4 byte per piksel.
interface foto {
    record ukuran {
        lebar: u32,
        tinggi: u32,
    }

    /// abu = (r × 77 + g × 150 + b × 29) >> 8, alfa tetap.
    grayscale: func(piksel: list<u8>) -> list<u8>;

    /// Thumbnail rata-rata area. Gagal kalau jumlah byte gak cocok dengan ukurannya.
    thumbnail: func(piksel: list<u8>, asal: ukuran, tujuan: ukuran) -> result<list<u8>, string>;
}

world olahfoto {
    export foto;
}

Build komponennya dari Rust

Proyeknya crate Rust biasa (olahfoto-komponen) dengan crate-type = ["cdylib"] dan satu dependency, wit-bindgen = "0.62". File WIT-nya di folder wit/.

wit_bindgen::generate!({ world: "olahfoto", path: "wit" });

use exports::homestay::kabut::foto::{Guest, Ukuran};

struct Olahfoto;

impl Guest for Olahfoto {
    fn grayscale(mut piksel: Vec<u8>) -> Vec<u8> {
        for p in piksel.chunks_exact_mut(4) {
            let abu = ((p[0] as u32 * 77 + p[1] as u32 * 150 + p[2] as u32 * 29) >> 8) as u8;
            (p[0], p[1], p[2]) = (abu, abu, abu);
        }
        piksel
    }

    fn thumbnail(piksel: Vec<u8>, asal: Ukuran, tujuan: Ukuran) -> Result<Vec<u8>, String> {
        let perlu = (asal.lebar * asal.tinggi * 4) as usize;
        if piksel.len() != perlu {
            return Err(format!("piksel harus {} byte, dapat {}", perlu, piksel.len()));
        }
        if tujuan.lebar == 0 || tujuan.tinggi == 0 || tujuan.lebar > asal.lebar || tujuan.tinggi > asal.tinggi {
            return Err(format!("ukuran tujuan {} × {} gak bisa dari {} × {}", tujuan.lebar, tujuan.tinggi, asal.lebar, asal.tinggi));
        }
        Ok(rata_rata_area(&piksel, asal, tujuan))
    }
}

export!(Olahfoto);
$ cargo build --release --target wasm32-wasip2
$ wasm-tools component wit target/wasm32-wasip2/release/olahfoto_komponen.wasm
world root {
  import wasi:io/[email protected];
  import wasi:cli/[email protected];
  ...                       (14 import wasi, dipotong)
  export homestay:kabut/[email protected];
}

Canonical ABI: dari list ke angka

Di dalam komponen, fungsinya tetap fungsi core Wasm yang cuma kenal angka. Canonical ABI adalah aturan baku untuk "menurunkan" (lower) nilai kaya jadi angka dan byte di linear memory, lalu "mengangkat" (lift) hasilnya balik: list dan string jadi pointer plus panjang, record dipecah jadi field-field-nya, result jadi satu byte penanda plus isinya. Karena aturannya sama untuk semua bahasa, Rust dan Go bisa saling memanggil tanpa sepakat apa pun lagi. Ini export core module di dalam olahfoto.wasm:

memory
homestay:kabut/[email protected]#grayscale        (i32, i32) -> i32
homestay:kabut/[email protected]#thumbnail        (i32, i32, i32, i32, i32, i32) -> i32
cabi_realloc                               (i32, i32, i32, i32) -> i32
cabi_post_homestay:kabut/[email protected]#grayscale   (i32)

Jadi jawaban tebakannya: dua angka i32, pointer dan panjang, dan hasilnya satu i32: alamat "return area" yang berisi pointer dan panjang hasilnya. Ini urutan satu panggilan grayscale([200, 120, 40, 255]), angkanya dari Lab:

Langkah Yang terjadi
1 cabi_realloc(0, 0, 1, 4) memberi 1114112: tempat 4 byte di memori komponen
2 glue menyalin 4 byte ke alamat 1114112
3 homestay:kabut/[email protected]#grayscale(1114112, 4) mengembalikan 1048912
4 baca 8 byte di 1048912: ptr 1114112, len 4
5 salin 4 byte keluar: [135, 135, 135, 255]
6 cabi_post_...#grayscale(1048912): komponen melepas memori hasilnya

Menjalankan: Wasmtime dan jco

$ wasmtime run --invoke 'grayscale([200, 120, 40, 255])' olahfoto.wasm
[135, 135, 135, 255]
$ wasmtime run --invoke 'thumbnail([1, 2, 3, 4], {lebar: 1200, tinggi: 800}, {lebar: 400, tinggi: 267})' olahfoto.wasm
err("piksel harus 3840000 byte, dapat 4")

Wasmtime 49.0.2 menjalankan komponen langsung, tanpa JavaScript. Di browser dan Node, pakai jco (1.37.0) dari Bytecode Alliance. jco transpile olahfoto.wasm -o olahfoto mengubah komponen versi no_std tadi jadi core module biasa (olahfoto.core.wasm, 7.139 byte) plus ES module JavaScript (olahfoto.js, 98,2 KiB sebelum minify) yang berisi canonical ABI di atas:

import { foto } from './olahfoto/olahfoto.js';

foto.grayscale(new Uint8Array([200, 120, 40, 255])); // Uint8Array [135, 135, 135, 255]
try {
  foto.thumbnail(new Uint8Array(16), { lebar: 1200, tinggi: 800 }, { lebar: 400, tinggi: 267 });
} catch (e) {
  console.log(e.message); // piksel harus 3840000 byte, dapat 16
}

📌 WASI 0.2, 0.3, dan dukungan runtime

⚠️ Jebakan yang sering ketemu

TL;DR: Component Model membungkus core module jadi komponen dengan kontrak WIT: package, interface, world, dan tipe kaya (list, string, record, result, option, variant, resource), sehingga modul dari bahasa berbeda bisa saling memanggil tanpa menyusun memori secara manual. Canonical ABI adalah aturan bakunya: list<u8> jadi dua i32 (pointer dan panjang) yang disalin lewat cabi_realloc, hasilnya dibaca dari return area, lalu dilepas dengan cabi_post_*; record dipecah jadi field-nya. Dari Rust: wit-bindgen 0.62 dengan cargo build --target wasm32-wasip2 (50.511 byte, ikut import WASI), atau wasm32-unknown-unknown plus wasm-tools component new (21.924 byte). Periksa dengan wasm-tools component wit. Wasmtime menjalankannya langsung (--invoke); browser dan Node gak mengenal komponen (expected version 01 00 00 00), jadi jco 1.37.0 mengubahnya jadi core module plus JS. WASI 0.2 dan 0.3 dibangun di atas komponen: wasi:cli/command, wasi:http/proxy, wasi:http/service.

Yang akan kamu pelajari