2 poin oleh GN⁺ 2025-02-05 | 1 komentar | Bagikan ke WhatsApp
  • Seorang pengembang menghabiskan 6 bulan pada malam hari dan akhir pekan untuk membuat VM WebAssembly berbasis C bernama Semblance, menjadikannya proyek belajar jangka panjang yang mendalami spesifikasi alih-alih side project singkat
  • WebAssembly adalah bytecode standar untuk menjalankan kode yang tidak tepercaya di browser dengan kecepatan mendekati native, dan penggunaannya di luar browser juga terus meningkat seperti pada Fastly, Shopify, dan Zed
  • Tujuan Semblance adalah mempelajari WebAssembly Core Specification dan bersiap untuk berkontribusi pada runtime industri seperti Wasmtime, sehingga mengimplementasikan semua opcode atau lolos core test suite tidak termasuk dalam cakupan
  • Interpreter bekerja dengan urutan memeriksa magic bytes dan versi 1 dari file .wasm, lalu decoding section, menyelesaikan import, menginstansiasi modul, mencari fungsi export, menginisialisasi stack dan activation frame, lalu menjalankan opcode switch loop
  • Karena ini bukan runtime yang lengkap, performanya lambat dan masih ada kebocoran memori serta kemungkinan rentan terhadap modul berbahaya, tetapi tujuan mempelajari spesifikasi berhasil dicapai lewat eksekusi Hello, World!

Mengapa Memilih WebAssembly

  • WebAssembly adalah himpunan instruksi standar dan format bytecode untuk mesin stack virtual
  • Titik awalnya adalah compiler C/C++ ke JavaScript milik Emscripten
    • Emscripten mengubah LLVM IR menjadi JavaScript sehingga kode C dan C++ bisa dijalankan di web
    • Pengembang browser dan proyek Emscripten membatasi kode hasil generasi ke subset JavaScript yang sederhana demi optimasi performa, dan subset ini kemudian distandardisasi sebagai asm.js
    • Setelah itu, untuk menghindari overhead parsing JavaScript, dirancang Wasm sebagai format bytecode mandiri
  • Belakangan ini penggunaan WebAssembly di luar browser juga meningkat
    • Edge Compute dari Fastly dan Functions dari Shopify dibangun di atas engine WebAssembly
    • Sistem ekstensi Zed juga berbasis Wasm
  • Bytecode Alliance memimpin upaya lintas industri untuk mendukung penggunaan WebAssembly di luar browser, sambil mengembangkan runtime Wasmtime dan standardisasi WASI
    • WASI memungkinkan modul WebAssembly berkomunikasi dengan sistem operasi host melalui host call yang mirip POSIX
    • Ini menyediakan cara standar untuk berinteraksi dengan antarmuka eksternal seperti input/output standar, filesystem, dan jaringan

Dari Side Project Menjadi Proyek Belajar

  • Ada pola berulang memulai side project baru lalu kehilangan minat dalam beberapa minggu dan berpindah ke proyek lain
  • Dibutuhkan proyek yang lebih besar, bisa ditekuni dalam jangka lebih panjang, dan menyentuh lapisan stack komputasi yang lebih rendah dibanding pekerjaan sehari-hari
  • Karena banyak side project terbengkalai akibat kurang arah, penulis mencari proyek dengan tujuan yang jelas dan hasil yang terlihat
  • Setelah merasa profil engineering yang generalis memengaruhi pencarian pekerjaan yang stabil, penulis mencari bidang spesialisasi yang bisa membentuk skillset berbentuk T
  • Fakta bahwa WebAssembly memungkinkan perancangan lingkungan komputasi kustom dengan system call khusus membuatnya tampak menarik sebagai alat platform engineering

Tujuan dan Cakupan Semblance

  • Nama proyek ini adalah Semblance, sebuah interpreter WebAssembly yang ditulis dalam C
  • Tujuan utamanya adalah memahami WebAssembly Core Specification
  • Dalam jangka panjang, tujuannya juga membangun pengetahuan tentang WebAssembly hingga cukup untuk berkontribusi pada runtime industri seperti Wasmtime
  • Karena ini proyek untuk belajar, tidak ada niat untuk mengimplementasikan semua opcode atau lolos core test suite
  • Bisa menjalankan “Hello, World!” sudah dianggap sebagai kriteria sukses yang memadai
  • Penulis mengakui tidak memiliki banyak pengalaman profesional dalam pengembangan C, kualitas kode C belum ideal, dan review kode sangat disambut

Alur Menjalankan Hello, World!

  • Kode C contoh mendeklarasikan fungsi eksternal puts dan mencetak "Hello, World!\n" di dalam hello()
  • WebAssembly dihasilkan dengan Clang
    • --target=wasm32
    • -nostdlib
    • -Wl,--no-entry
    • -Wl,--export-all
    • -Wl,--allow-undefined
    • -O3
  • File hello.wasm yang dihasilkan dijalankan dengan semblance hello.wasm --invoke hello
  • Saat program dimulai, stack, argumen command line, modul WebAssembly yang sudah didecode, dan memori untuk store yang menyimpan status runtime diinisialisasi
  • Argumen command line diparse, dan jika tidak valid program akan keluar

Decoding Biner dan Struktur Modul

  • Interpreter pertama-tama mendecode format biner WebAssembly
  • Di awal file, interpreter memeriksa magic bytes "\0asm"
  • Interpreter memverifikasi bahwa versi format binernya adalah 1
  • Setelah itu interpreter mendecode section yang disertakan
  • Setelah decoding selesai, struct WasmModule akan terisi dengan fungsi, tipe, import, data, dan elemen lain dari hello.wasm
    • types
    • funcs
    • tables
    • mems
    • globals
    • elems
    • datas
    • start
    • imports
    • exports
    • customs
    • meta

Menyelesaikan Import dan env::puts

  • Fungsi host yang saat ini disediakan Semblance hanya satu, yaitu env::puts
  • Import fungsi host ditangani dengan mengalokasikan native function pointer di store, lalu memasukkan funcaddr yang dihasilkan ke array imports milik modul
  • hostcall_puts menggunakan offset i32 yang diterima sebagai argumen
    • Mengambil pointer data dari instance memory pertama di store
    • Mencetak string pada posisi offset ke output standar dengan printf("%s", ...)
  • Saat menelusuri daftar import, jika ditemukan entri dengan module name "env" dan item name "puts", fungsi host akan didaftarkan melalui register_hostcall_puts

Instansiasi dan Eksekusi Opcode

  • Setelah import diselesaikan, modul kemudian diinstansiasi
  • Instansiasi mencakup tahap validasi
    • Melakukan pemeriksaan tipe dan pengujian untuk memastikan modul memiliki bentuk yang benar
    • Menginisialisasi memory, global, dan table
    • Jika ada fungsi start, fungsi itu akan dipanggil
  • Hasil instansiasi adalah WasmModuleInst, yang menyimpan struktur saat eksekusi dan export yang siap digunakan
  • Fungsi export dicari berdasarkan nama yang ditentukan oleh argumen command line --invoke
    • Menelusuri export dan mengembalikan WasmExportInst yang namanya cocok
    • Memastikan export yang ditemukan adalah function
  • Pemanggilan fungsi memerlukan inisialisasi stack dan activation frame
  • Inti eksekusi adalah opcode switch loop
    • Membaca instruction Wasm dalam body fungsi satu per satu
    • Melakukan operasi pada stack dan store sesuai opcode
    • Contohnya menangani i32.const, i64.const, f32.const, f64.const, i32.ge_s, call, nop, unreachable, end, dan lain-lain
    • Opcode yang belum ditangani akan mencetak "unhandled opcode [...]" lalu mengembalikan trap

Instruksi call dan Pemanggilan Fungsi Host

  • Penanganan WasmOpCall menggunakan funcidx, yaitu nilai immediat dari opcode call, untuk menemukan function instance di store
  • Berdasarkan informasi tipe fungsi, interpreter melakukan pop argumen yang diperlukan dari stack
  • Jenis function instance diperiksa untuk membedakan apakah itu fungsi Wasm atau native host function
  • Jika itu fungsi host, hostfunc function pointer yang tersimpan akan dijalankan sebagai native call
  • Jika ada nilai balik, nilainya akan di-push kembali ke stack
  • Ketika opcode end dari fungsi hello tercapai, opcode switch loop berhenti dan kontrol kembali ke main

Hasil dan Keterbatasan yang Masih Tersisa

  • Hasil eksekusi ditampilkan ke output standar sebagai berikut
    • Hello, World!
    • Ok []
  • Proyek ini dapat menjalankan program “Hello, World!” yang sederhana
  • Cakupan opcode belum lengkap
  • Kodenya berantakan, lambat, dan memiliki kebocoran memori
  • Bisa rentan terhadap modul berbahaya
  • Meski begitu, penulis banyak belajar tentang WebAssembly Core Specification dan merasa terbantu untuk keluar dari area yang sudah familiar sebagai engineer
  • Setelah membuat interpreter sendiri, penulis merasa telah memperoleh pengetahuan WebAssembly yang cukup untuk bisa berkontribusi pada runtime industri seperti Wasmtime
  • Ke depan masih mungkin menambahkan beberapa opcode lagi, tetapi penulis ingin menutup fase menjadikan proyek ini sebagai proyek utama

1 komentar

 
GN⁺ 2025-02-05
Komentar Hacker News
  • Dulu saya pernah membuat interpreter Wasm dengan Scheme, jadi senang melihat makin banyak orang yang mengimplementasikannya sendiri
    Ternyata tidak sesulit yang dibayangkan, jadi sebaiknya coba lihat spesifikasinya dan mencobanya; tidak perlu mengimplementasikan semua instruksi, cukup sampai terasa menyenangkan saja

  • Sebagai sesama implementor, ada tips: di spec-test ada banyak format Wasm teks yang aneh dan cara kompilasinya tidak intuitif, tetapi jika memakai konverter wast2json, Anda bisa membuat deskripsi JSON yang lebih sederhana bersama berkas Wasm biner biasa

    • Di Chicory juga kami memakai cara yang sama: https://github.com/dylibso/chicory
      Semakin cepat test suite ini bisa dijalankan, semakin membantu kecepatan iterasi dan akurasi
      Memang butuh waktu hingga semuanya berjalan, tetapi begitu berhasil, kami sangat cepat mencapai titik bisa menjalankan apa pun, dan meski test suite-nya tidak lengkap, cakupannya sekitar 95%: https://github.com/WebAssembly/testsuite
    • Saya juga sedang membuat compiler, dan ini membantu
      Saya pernah melihat pengujian ini sebelumnya, tetapi kesal dan bingung karena tidak tahu cara memakainya
  • Terkait interpretasi Wasm secara langsung, makalah ini cukup menarik: https://arxiv.org/abs/2205.01183
    Berdasarkan itu saya membuat https://github.com/peterseymour/winter, dan belajar bahwa Wasm tidak sesederhana yang saya kira

  • Pertanyaan pemula: saya penasaran bagaimana debugging interpreter dilakukan saat kita tidak menulis kode langsung untuk targetnya
    Saya juga penasaran sejauh mana pendekatan fuzzing string opcode bisa membantu, seberapa besar perbedaan praktis antara mesin Wasm sisi server dan mesin berbasis browser, serta seberapa banyak pekerjaan yang diperlukan untuk mengganti yang satu dengan yang lain

  • Pendekatan yang menarik dan pekerjaan yang bagus
    Jika ingin melihat intinya, sebagian besar ada di berkas ini: https://github.com/irrio/semblance/blob/main/src/wrun.c
    Kalau dipikir-pikir, mungkin akan bagus jika proyek ini mengikuti Wasm-C-API(https://github.com/WebAssembly/wasm-c-api) sebagai antarmuka standar
    Itu sudah berupa C API dan diadopsi oleh sebagian besar runtime Wasm seperti Wasmer, V8, dan wasmi, sehingga pengembang yang familier dengan API itu bisa lebih mudah mencobanya
    Jika penulisnya cukup akrab dengan Wasm dan ingin berkontribusi ke Wasmer, patch atau perbaikan juga diterima

    • Penulisnya jelas terlihat cukup akrab dengan Wasm, dan kemungkinan juga cukup tahu untuk menghindari perusahaan yang pernah mencoba merek dagang WebAssembly
      Mereka menyebut “kekhawatiran yang dapat dimengerti atas fakta bahwa Wasmer, yang didanai VC, mencoba mendaftarkan WebAssembly, nama organisasi nirlaba, sebagai merek dagang”, dan itu berarti mengakui kesalahan
    • Wasmer punya Installed-Size: 266 MB; ini sebenarnya apa
    • Setahu saya, agak jauh untuk mengatakan Wasmer mengadopsi Wasm-C-API
      https://github.com/wasmerio/wasmer/issues/2615 dibiarkan begitu saja lalu ditutup otomatis
  • Ini pertanyaan yang agak lebih kontroversial: saya penasaran apakah ada rencana menambahkan instruksi tail call sementara
    Di sisi spesifikasi Wasm, itu ditolak karena dianggap terlalu “tingkat tinggi”, tetapi komite C juga pernah menolak usulan Dennis Ritchie
    Tetap saja, saya masih akan mempertaruhkan uang saya di pihak Ritchie, dan sepertinya Rob Pike juga bertaruh ke arah itu
    Kalau tidak, mengapa ia membuat Golang? Tail call hanya menjadi tingkat tinggi jika call-nya juga tingkat tinggi

  • Sebaiknya coba lihat Orca. Sepertinya Anda bisa berkontribusi dengan baik di sana
    [0] https://orca-app.dev

  • Saya juga memutuskan untuk fokus pada satu proyek alih-alih berpindah-pindah di antara hal-hal baru yang mengilap, tetapi sebagai pengecualian saya menyuruh asisten AI mengerjakan tugas-tugas remeh
    Bisa dibilang, cukup membuat frustrasi
    Dan saya juga ingin menyarankan memakai library seperti libffi untuk menangani banyak argumen dan semacamnya saat memanggil dari C

  • Saat memakai WebAssembly sebagai API plugin di tempat seperti zed, saya penasaran bagaimana pengembang plugin men-debug kode mereka
    Misalnya, apakah debugging dengan breakpoint memungkinkan, dan apakah bisa mendapatkan stack trace jika kode crash