Mengembangkan VM WebAssembly yang Ditulis dalam C
(irreducible.io)- 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
putsdan mencetak"Hello, World!\n"di dalamhello() - WebAssembly dihasilkan dengan Clang
--target=wasm32-nostdlib-Wl,--no-entry-Wl,--export-all-Wl,--allow-undefined-O3
- File
hello.wasmyang dihasilkan dijalankan dengansemblance 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
WasmModuleakan terisi dengan fungsi, tipe, import, data, dan elemen lain darihello.wasmtypesfuncstablesmemsglobalselemsdatasstartimportsexportscustomsmeta
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
funcaddryang dihasilkan ke arrayimportsmilik modul hostcall_putsmenggunakan 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 melaluiregister_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
WasmExportInstyang namanya cocok - Memastikan export yang ditemukan adalah function
- Menelusuri export dan mengembalikan
- 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
WasmOpCallmenggunakanfuncidx, yaitu nilai immediat dari opcodecall, 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,
hostfuncfunction pointer yang tersimpan akan dijalankan sebagai native call - Jika ada nilai balik, nilainya akan di-push kembali ke stack
- Ketika opcode
enddari fungsihellotercapai, opcode switch loop berhenti dan kontrol kembali kemain
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
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 biasaSemakin 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 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
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
Installed-Size: 266 MB; ini sebenarnya apahttps://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
libffiuntuk menangani banyak argumen dan semacamnya saat memanggil dari CSaat 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