C ile yazılmış bir WebAssembly VM geliştirme
(irreducible.io)- Bir geliştirici, C tabanlı WebAssembly VM Semblance'ı 6 ay boyunca akşamlarını ve hafta sonlarını ayırarak geliştirdi; kısa sürede biten bir yan proje yerine, spesifikasyonu derinlemesine inceleyen uzun soluklu bir öğrenme projesi olarak ilerletti
- WebAssembly, tarayıcıda güvenilmeyen kodu native'e yakın hızda çalıştırmak için standart bir bytecode'dur ve Fastly, Shopify ve Zed gibi örneklerle tarayıcı dışı kullanım da artmaktadır
- Semblance'ın amacı WebAssembly Core Specification'ı öğrenmek ve Wasmtime gibi endüstriyel runtime'lara katkı verebilecek seviyede hazırlanmak olduğundan, tüm opcode'ları uygulamak veya core test suite'ten geçmek kapsam dışında bırakıldı
- Yorumlayıcı,
.wasmdosyasındaki magic bytes ve sürüm 1'i doğruladıktan sonra section decoding, import çözümleme, modül instance'lama, export fonksiyonu bulma, stack ve activation frame başlatma, opcode switch loop çalıştırma sırasıyla ilerler - Tam teşekküllü bir runtime olmadığı için yavaş, bellek sızıntıları içeriyor ve kötü amaçlı modüllere karşı zafiyet taşıyabiliyor; ancak Hello, World! çalıştırarak spesifikasyonu öğrenme hedefini gerçekleştirmiş durumda
Neden WebAssembly seçildi
- WebAssembly, sanal stack machine için standart bir komut kümesi ve bytecode formatıdır
- Başlangıç noktası Emscripten'in C/C++ to JavaScript derleyicisiydi
- Emscripten, LLVM IR'i JavaScript'e dönüştürerek C ve C++ kodunun web'de çalışmasını sağladı
- Tarayıcı geliştiricileri ve Emscripten projesi, performans optimizasyonu için üretilen kodu basit bir JavaScript alt kümesiyle sınırladı ve bu alt küme asm.js olarak standartlaştı
- Daha sonra JavaScript parse overhead'inden kaçınmak için bağımsız bir bytecode formatı olan Wasm tasarlandı
- Son dönemde WebAssembly'nin tarayıcı dışı kullanımı da artıyor
- Fastly'nin Edge Compute'u ve Shopify'ın Functions yapısı WebAssembly motorları üzerine kurulu
- Zed'in uzantı sistemi de Wasm tabanlı
- Bytecode Alliance, WebAssembly'nin tarayıcı dışında kullanılmasını destekleyen endüstri çapındaki çabaları yönlendiriyor; Wasmtime runtime'ı ve WASI standardizasyonu üzerinde çalışıyor
- WASI, WebAssembly modüllerinin POSIX benzeri host call'lar aracılığıyla host işletim sistemiyle iletişim kurmasını sağlar
- Standart girdi/çıktı, dosya sistemi, ağ gibi dış arayüzlerle etkileşim için standart bir yol sunar
Yan projeden öğrenme projesine
- Tekrarlayan bir biçimde yeni yan projeler başlatıp birkaç hafta sonra ilgisini kaybederek başka projelere geçme alışkanlığı vardı
- Daha uzun süre odaklanabileceği ve günlük işine kıyasla hesaplama yığınının daha alt seviyelerini ele alan büyük bir projeye ihtiyaç duyuyordu
- Yön eksikliği yüzünden yarım bırakılmış çok sayıda yan proje olduğu için, net hedefleri ve görünür çıktıları olan bir proje arıyordu
- Generalist mühendislik profilinin istikrarlı iş bulmasını etkilediğini düşündükten sonra, T-şekilli beceri seti oluşturabileceği bir uzmanlık alanı aramaya başladı
- WebAssembly'de özel system call'larla özelleştirilmiş bir hesaplama ortamı tasarlanabilmesi, onu platform mühendisliği aracı olarak cazip kıldı
Semblance'ın hedefi ve kapsamı
- Projenin adı Semblance ve C ile yazılmış bir WebAssembly yorumlayıcısı
- Ana hedef, WebAssembly Core Specification'a aşina olmaktı
- Uzun vadede Wasmtime gibi endüstriyel runtime'lara katkı verebilecek düzeyde WebAssembly bilgisi edinmek de amaçlandı
- Bu bir öğrenme projesi olduğu için tüm opcode'ları uygulamak veya core test suite'ten geçmek hedeflenmedi
- Başarı ölçütü olarak “Hello, World!” çalıştırabilmek yeterli görüldü
- Profesyonel C geliştirme deneyimi olmadığını ve C kodunun kalitesinin ideal olmadığını kabul ediyor; code review memnuniyetle karşılanıyor
Hello, World! çalıştırma akışı
- Örnek C kodu, harici
putsfonksiyonunu bildiriyor vehello()içinde"Hello, World!\n"çıktısını veriyor - Clang ile WebAssembly üretiliyor
--target=wasm32-nostdlib-Wl,--no-entry-Wl,--export-all-Wl,--allow-undefined-O3
- Oluşturulan
hello.wasm,semblance hello.wasm --invoke hellokomutuyla çalıştırılıyor - Program başlatıldığında stack üzerinde komut satırı argümanları, decode edilmiş WebAssembly modülü ve runtime durumunu tutan store için bellek başlatılıyor
- Komut satırı argümanları parse ediliyor ve hatalıysa program sonlandırılıyor
Binary decoding ve modül yapısı
- Yorumlayıcı önce WebAssembly binary format'ını decode ediyor
- Dosyanın başındaki
"\0asm"magic bytes doğrulanıyor - Binary format sürümünün
1olup olmadığı kontrol ediliyor - Ardından içerdiği sections decode ediliyor
- Decode tamamlandığında
WasmModuleyapısıhello.wasmiçindeki fonksiyonlar, tipler, import'lar, data ve diğer alanlarla dolduruluyortypesfuncstablesmemsglobalselemsdatasstartimportsexportscustomsmeta
Import çözümleme ve env::puts
- Semblance'ın şu anda sağladığı tek host fonksiyonu
env::puts - Host fonksiyonu import'u, store içinde native function pointer ayırıp oluşan
funcaddrdeğerini modülünimportsdizisine yerleştirerek işleniyor hostcall_puts, argüman olarak aldığı i32 offset'i kullanıyor- Store'un ilk memory instance'ından data pointer alıyor
- Offset konumundaki string'i
printf("%s", ...)ile standart çıktıya yazdırıyor
- Import listesi dolaşılırken module name
"env"ve item name"puts"olan kayıt bulunursa, host fonksiyonuregister_hostcall_putsile kaydediliyor
Instance'lama ve opcode yürütme
- Import çözümlemesinden sonra modül instance ediliyor
- Instance'lama bir doğrulama aşaması içeriyor
- Tip kontrolü ve modülün doğru biçimde olup olmadığını sınayan testler yapılıyor
- Memory, global ve table başlatılıyor
startfonksiyonu varsa çağrılıyor
- Instance'lama sonucu
WasmModuleInst; bu yapı yürütme zamanı durumunu ve kullanıma hazır export'ları içeriyor --invokekomut satırı argümanında verilen ada göre export fonksiyonu bulunuyor- Export'lar dolaşılarak adı eşleşen
WasmExportInstdöndürülüyor - Dönen export'un bir function olup olmadığı doğrulanıyor
- Export'lar dolaşılarak adı eşleşen
- Fonksiyon çağrısı için stack ve activation frame başlatılması gerekiyor
- Çalıştırmanın çekirdeği opcode switch loop
- Fonksiyon gövdesindeki Wasm instructions sırayla okunuyor
- Opcode'a göre stack ve store üzerinde işlemler uygulanıyor
- Örnek olarak
i32.const,i64.const,f32.const,f64.const,i32.ge_s,call,nop,unreachable,endişleniyor - Ele alınmayan opcode'lar için
"unhandled opcode [...]"yazdırılıyor ve trap döndürülüyor
call komutu ve host fonksiyonu çağrısı
WasmOpCallişleyicisi,callopcode'unun immediate değeri olanfuncidxile store içinden function instance'ı buluyor- Function type bilgisine göre gerekli argümanlar stack'ten pop ediliyor
- Function instance türü kontrol edilerek bunun bir Wasm fonksiyonu mu yoksa native host function mı olduğu ayırt ediliyor
- Host fonksiyonuysa, saklanan
hostfuncfunction pointer'ı native call olarak çalıştırılıyor - Dönüş değeri varsa yeniden stack'e push ediliyor
hellofonksiyonununendopcode'una ulaşıldığında opcode switch loop sona eriyor ve kontrolmain'e dönüyor
Sonuç ve kalan sınırlamalar
- Çalıştırma sonucu standart çıktıda şu şekilde görünüyor
Hello, World!Ok []
- Proje basit bir “Hello, World!” programını çalıştırabiliyor
- Opcode kapsamı tam değil
- Kod dağınık, yavaş ve bellek sızıntıları içeriyor
- Kötü amaçlı modüllere karşı savunmasız olabilir
- Buna rağmen WebAssembly Core Specification hakkında çok şey öğrenildi ve bir mühendis olarak alışılmış alanın dışına çıkmaya yardımcı oldu
- Kendi yorumlayıcısını yazdıktan sonra, Wasmtime gibi endüstriyel runtime'lara katkı verebilecek kadar WebAssembly bilgisi edindiğini düşünüyor
- Bundan sonra birkaç opcode daha eklemeyi sürdürebilir, ancak bu projeyi ana proje olarak devam ettirme aşamasını sonlandırmak istiyor
1 yorum
Hacker News yorumları
Daha önce Scheme ile bir Wasm yorumlayıcısı yapmıştım; bunu doğrudan uygulayan daha fazla kişi görmek sevindirici.
Sandığınız kadar zor değil; spesifikasyona bir göz atıp denemenizi öneririm. Tüm komutları uygulamanız gerekmiyor, keyif alacak kadarını yapmak bile yeterli.
Aynı türden bir uygulayıcı olarak bir ipucu vereyim: spec-test içinde derleme yöntemi pek sezgisel olmayan tuhaf metinsel Wasm biçimleri çok, ama
wast2jsondönüştürücüsünü kullanırsanız normal ikili Wasm dosyalarıyla birlikte daha basit bir JSON açıklaması üretebilirsiniz.Bu test paketini ne kadar hızlı çalıştırabilir hale getirirseniz, yineleme hızına ve doğruluğa o kadar yardımcı olur.
Hepsini çalışır hale getirmek biraz zaman aldı, ama bir kez başardıktan sonra her şeyi çalıştırabilecek noktaya çok hızlı ulaştık; test paketi eksiksiz olmasa da yaklaşık %95’ini kapsıyor: https://github.com/WebAssembly/testsuite
Bu testleri daha önce görmüştüm ama nasıl kullanacağımı bilmediğim için sinir bozucu ve kafa karıştırıcı gelmişti.
Doğrudan Wasm yorumlama ile ilgili olarak şu makale oldukça ilginçti: https://arxiv.org/abs/2205.01183
Buna dayanarak https://github.com/peterseymour/winter projesini yaptım ve Wasm’ın düşündüğüm kadar basit olmadığını öğrendim.
Acemi sorusu: Doğrudan hedef olarak kod yazmadığınız bir durumda yorumlayıcı hata ayıklaması nasıl yapılıyor, merak ediyorum.
İşlem kodu dizgilerini fuzzing’den geçirmek ne kadar işe yarıyor, ayrıca sunucu tarafı Wasm motorları ile tarayıcı tabanlı motorlar arasında pratikte ne kadar büyük fark var ve birini diğerine dönüştürmek ne kadar iş gerektirir, bunları da merak ediyorum.
İlginç bir yaklaşım ve harika bir çalışma.
Özünü görmek isteyenler için çoğu şu dosyada: https://github.com/irrio/semblance/blob/main/src/wrun.c
Düşününce, projenin standart arayüzü olarak Wasm-C-API’yi (https://github.com/WebAssembly/wasm-c-api) izlemesi iyi olurdu gibi geliyor.
Zaten bir C API’si ve Wasmer, V8, wasmi gibi çoğu Wasm çalışma zamanı tarafından benimsenmiş durumda; bu yüzden o API’ye aşina geliştiriciler daha kolay deneyebilir.
Yazar Wasm’a yeterince aşinaysa ve Wasmer’a katkıda bulunmak isterse yamalar veya iyileştirmeler de memnuniyetle karşılanır.
“VC yatırımı almış Wasmer’ın kâr amacı gütmeyen kuruluşun adı olan WebAssembly’yi ticari marka olarak tescil ettirmeye çalışmasına yönelik anlaşılır endişe” denmişti; bu da hatayı kabul etmek demek.
Installed-Size: 266 MBolması da neyin nesi?https://github.com/wasmerio/wasmer/issues/2615 ihmal edildi ve sonra otomatik olarak kapatıldı.
Biraz daha tartışmalı bir soru: geçici tail call komutları eklemeyi düşünüyor musunuz, merak ediyorum.
Wasm spesifikasyonu tarafında bunun fazla “yüksek seviyeli” olduğu gerekçesiyle reddedilmişti, ama C komitesi de bir zamanlar Dennis Ritchie’nin önerisini reddetmişti.
Yine de ben paramı hâlâ Ritchie’den yana koyardım; Rob Pike da sanırım o yönde bahse girmişti.
Öyle olmasaydı Golang’i neden yapsın ki? Çağrı yalnızca yüksek seviyeli olduğunda tail call da yüksek seviyelidir.
Orca’ya bir bakmanız iyi olabilir. Oraya iyi katkı verebilecek gibi görünüyorsunuz.
[0] https://orca-app.dev
Ben de yeni ve parlak şeyler arasında dolaşmak yerine tek bir projeye odaklanmaya karar verdim; istisna olarak yapay zeka yardımcılarına angarya işleri yaptırıyorum.
En hafif ifadeyle, epey sinir bozucu.
Ayrıca C ile çağrı yaparken birden çok argümanı vb. işlemek için
libffigibi bir kütüphane kullanmayı da önerecektim.WebAssembly zed gibi yerlerde eklenti API’si olarak kullanıldığında, eklenti geliştiricilerinin kodu nasıl hata ayıkladığını merak ediyorum.
Örneğin breakpoint ile hata ayıklama mümkün mü, kod çökerse stack trace alınabiliyor mu, bunları merak ediyorum.