WASI (Wasm di Server) — WebAssembly

WASI: WebAssembly di Luar Browser WASI (WebAssembly System Interface) memungkinkan Wasm berjalan di server, CLI, dan embedded systems — bukan hanya browser…

WASI (WebAssembly System Interface) adalah daftar fungsi standar yang boleh di-import modul Wasm untuk bicara dengan sistem: membaca argumen, menulis ke layar terminal, membuka file, membaca jam, mengambil angka acak. Dengan WASI, .wasm yang sama bisa jalan di server dan di terminal, bukan cuma di browser. Bayangin Bu Sri menyewa tukang dari agen untuk mengolah foto kamar. Tukangnya bekerja di satu ruangan, dan semua permintaan harus lewat formulir standar ke Bu Sri. Bu Sri cuma memberi kunci lemari foto. Lemari rahasia berisi data tamu gak pernah dikasih kuncinya, jadi tukangnya bahkan gak tahu lemari itu ada. Tukangnya modul Wasm, formulirnya fungsi WASI, Bu Sri adalah runtime (host), dan kunci lemari itu folder yang di-preopen.

🧪 Tebakan cepat: program foto Bu Sri dijalankan dengan izin ke folder foto saja (--dir foto), lalu diminta membaca foto/../rahasia/tamu.csv. Path-nya diawali foto/. Berhasil atau ditolak? Tebak dulu, jawabannya ada di bagian "Capability: izin per folder".

Program foto yang sama, sekarang di terminal

Ini foto-abu, program Rust biasa yang memakai std::fs. Gak ada wasm-bindgen, gak ada JavaScript:

//! foto-abu: jadikan foto RGBA abu-abu. Jalan di server lewat WASI.
use std::{env, fs, process};

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        eprintln!("pakai: foto-abu <masuk.rgba> <keluar.rgba>");
        process::exit(2);
    }
    let mut px = match fs::read(&args[1]) {
        Ok(isi) => isi,
        Err(e) => {
            eprintln!("gagal membaca {}: {e}", args[1]);
            process::exit(1);
        }
    };
    for p in px.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] = abu;
        p[1] = abu;
        p[2] = abu;
    }
    if let Err(e) = fs::write(&args[2], &px) {
        eprintln!("gagal menulis {}: {e}", args[2]);
        process::exit(1);
    }
    println!("{}: {} piksel jadi abu-abu", args[2], px.len() / 4);
}
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1   # target/wasm32-wasip1/release/foto-abu.wasm, 63.172 byte

Menjalankan dengan Wasmtime

Wasmtime adalah runtime WASI dari Bytecode Alliance. Ini keluaran aslinya:

$ wasmtime --version
wasmtime 49.0.2 (3c8a3e79a 2026-10-02)
$ wasmtime foto-abu.wasm foto/kamar-teras.rgba foto/kamar-teras-abu.rgba
gagal membaca foto/kamar-teras.rgba: No such file or directory (os error 44)
$ wasmtime --dir foto foto-abu.wasm foto/kamar-teras.rgba foto/kamar-teras-abu.rgba
foto/kamar-teras-abu.rgba: 960000 piksel jadi abu-abu
$ wasmtime --dir foto::/data foto-abu.wasm /data/kamar-teras.rgba /data/kamar-teras-abu.rgba
/data/kamar-teras-abu.rgba: 960000 piksel jadi abu-abu

Capability: izin per folder

WASI memakai model capability: modul cuma bisa memakai apa yang diberikan host secara eksplisit. Bawaannya: gak ada file, gak ada jaringan, gak ada environment variable. Yang ada cuma argumen, stdin, stdout, stderr, jam, dan angka acak.

$ wasmtime --dir foto foto-abu.wasm rahasia/tamu.csv foto/curi.rgba
gagal membaca rahasia/tamu.csv: No such file or directory (os error 44)
$ wasmtime --dir foto foto-abu.wasm foto/../rahasia/tamu.csv foto/curi.rgba
gagal membaca foto/../rahasia/tamu.csv: Operation not permitted (os error 63)

Jadi jawaban tebakannya: ditolak. foto/../rahasia/tamu.csv dibuka lewat folder foto, dan .. gak boleh keluar dari folder yang diizinkan. Wasmtime menolaknya dengan os error 63 (perm). Bahkan kalau --dir rahasia juga diberikan, path yang lewat foto/.. tetap ditolak; yang boleh cuma rahasia/tamu.csv langsung. Bandingkan dengan program native: dia bisa membaca semua file yang bisa dibaca user yang menjalankannya.

Node: node:wasi

Node punya modul node:wasi untuk menjalankan modul WASI preview 1:

// jalankan.mjs
import { readFile } from 'node:fs/promises';
import { WASI } from 'node:wasi';

const wasi = new WASI({
  version: 'preview1', // wajib: 'preview1' (atau 'unstable')
  args: ['foto-abu', 'foto/kamar-teras.rgba', 'foto/kamar-teras-abu.rgba'],
  preopens: { foto: './foto' }, // nama di dalam modul: folder di host
});
const modul = await WebAssembly.compile(await readFile('foto-abu.wasm'));
const instance = await WebAssembly.instantiate(modul, wasi.getImportObject());
process.exitCode = wasi.start(instance); // _start = main() dari Rust; hasilnya kode keluar
$ node jalankan.mjs
(node:91328) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
foto/kamar-teras-abu.rgba: 960000 piksel jadi abu-abu

Preview 1, WASI 0.2, WASI 0.3

node:wasi cuma mengerti preview 1. Komponen ditolak sejak byte ke-4:

$ node jalankan-comp.mjs   # jalankan.mjs, tapi membaca foto-abu-component.wasm
(node:91330) ExperimentalWarning: WASI is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/modules/run_main:123
    triggerUncaughtException(
    ^

[CompileError: WebAssembly.compile(): expected version 01 00 00 00, found 0d 00 01 00 @+4]

Komponen dibahas lengkap di pelajaran "Wasm Component Model".

📌 Ukuran dan waktu mulai

Diukur di MacBook M1 Pro yang sedang sibuk, waktu satu proses penuh (mulai, baca foto, olah, tulis), median 11 putaran yang dijalankan bergantian:

Cara Ukuran Waktu
Program native Rust (arm64) 302.656 byte 5,9 ms
Wasmtime, hasil compile di-cache (bawaannya) 63.172 byte 15,1 ms
Wasmtime, -C cache=n (compile tiap kali) 63.172 byte 27,4 ms
Wasmtime, .cwasm dari wasmtime compile 215.584 byte 16,1 ms
Wasmtime, komponen WASI 0.2 89.702 byte 19,4 ms
Node 22, node jalankan.mjs 63.172 byte 40,3 ms (Node kosong 29,4 ms)
TinyGo 0.43 (-target=wasip1) 302.365 byte
Go 1.27.2 (GOOS=wasip1) 2.677.059 byte

⚠️ Jebakan yang sering ketemu

TL;DR: WASI adalah antarmuka standar antara modul Wasm dan sistem: argumen, stdout, file, jam, angka acak. Program Rust biasa di-build dengan --target wasm32-wasip1 (63.172 byte, 13 import wasi_snapshot_preview1) dan dijalankan dengan wasmtime (49.0.2) atau node:wasi (masih Experimental, version: 'preview1' wajib). Model keamanannya capability: tanpa --dir foto (atau preopens) modul gak melihat file apa pun (os error 44), dan foto/../rahasia tetap ditolak (os error 63 di Wasmtime, 76 di Node). Preview 1 dukungannya paling luas; WASI 0.2 (Januari 2024) dan 0.3 (Juni 2026) memakai Component Model, hasilnya komponen yang dijalankan Wasmtime tapi ditolak node:wasi. Satu proses Wasmtime butuh sekitar 15 ms dengan cache atau .cwasm (27 ms kalau compile tiap kali, native 6 ms), sedangkan instance baru di runtime yang sudah jalan butuh kurang dari 1 ms.

Yang akan kamu pelajari