- Jawsm, Rust ile yazılmış bir JavaScript→WebAssembly derleyicisidir; yorumlayıcı olmadan çalıştırılabilen bağımsız WASM ikilileri üreten deneysel bir araçtır
- porffor’a benzer şekilde standalone WASM üretir, ancak uygulama yaklaşımı farklıdır; JavaScript söz dizimini WASM komutlarına dönüştürmek için güncel WASM GC, exception handling ve tail call optimization önerilerindeki komutlardan yararlanır
- Şu anda test262 test paketinin yaklaşık %25’ini geçiyor; uygulanabilirliği doğrulamak açısından önemli görülen scopes/closures, try/catch, async/await ve generators uygulanmış durumda
- Henüz üretim kullanımına hazır değil; birçok dil özelliği ve yerleşik tip eksik ya da tamamlanmamış durumda, RegExp, builtins’in çoğu ve BigInt arithmetic de eksik
- Üretilen ikililer, güncel WASM önerilerine bağımlılık nedeniyle runtime’lar arasında düşük taşınabilirliğe sahip; şu anda V8 tabanlı Chromium veya Node üzerinde WASIp2 polyfill ile çalıştırma yöntemi kullanılıyor
Jawsm’in hedefi ve konumu
- Jawsm, “awesome” gibi telaffuz edilen bir JavaScript to WebAssembly derleyicisidir
- Rust ile yazılmıştır ve JavaScript kodunu yorumlayıcı olmadan çalıştırılabilen standalone WASM binary haline getirmeyi hedefler
- porffor ile benzer çıktılar üretir, ancak uygulama yaklaşımı farklıdır
- Şu anda deneysel bir araçtır ve production’da kullanılmaya hazır değildir
- Birçok JavaScript dil özelliği eksik ya da tamamlanmamış durumdadır
- Birçok yerleşik tip ve metot da eksik ya da tamamlanmamış durumdadır
- Uzun vadeli hedef, JavaScript dil özelliklerini %100 desteklemektir
Jawsm neden geliştirildi?
- Proje, WebAssembly senaryolarını çalıştıran stres testi aracı Crows üzerinde çalışılırken başladı
- Crows şu anda yalnızca Rust’tan WASM’e derlenmiş kodu destekliyor
- Küçük testleri yazmak çoğu zaman interpreted language ile daha kolaydır, ancak WASM üzerinde scripting language çalıştırma biçimi şu anda ideal değildir
- Yorumlayıcı dahil edildiğinde ikili dosya boyutu en az birkaç MB olur ve bellek kullanımı da daha yüksek hale gelir
- Ya da TinyGo, AssemblyScript gibi hedef dilin bir varyantını kullanmak gerekir
- Modern WASM önerilerinden yararlanarak, derlenmiş bir yorumlayıcı olmadan da JavaScript özelliklerinin %100 uygulanabileceği hedefleniyor
- WASM runtime’ın kendisinin zaten bir yorumlayıcı olduğu varsayımına dayanır
Şu anda çalışan özellikler
- Jawsm şu anda test262 test paketinin yaklaşık %25’ini geçiyor
- Projenin uygulanabilirliğini doğrulamak için kritik görülen 4 temel özelliğin tamamı uygulanmış durumda
- scopes/closures
- try/catch
- async/await
- generators
- Bunun dışında çalışması gereken özellikler şunlardır
var, let, const bildirimleri ve atamaları
do..while, while, for, for..in, for..of döngüleri
switch ifadeleri
break, continue için sınırlı destek
- string literal’lar ve string literal toplama
- sayılar ve temel operatörler
+, -, *, /
- boolean ve temel boolean operatörleri
- diziler ve Array ile ilgili fonksiyonların çoğu
- object literals
new anahtar sözcüğü
async, await
Promise API için sınırlı destek
- generator functions
try/catch
- Çok temel BigInt desteği
Hâlâ eksik olan özellikler
- Şu anda eksik olan başlıca kalemler şunlardır
- builtins’lerin çoğu
- Mevcut builtins’lerdeki metotların çoğu
- RegExp expressions
- BigInt arithmetic
- Sonraki aşama planı şu özelliklerin uygulanmasına odaklanıyor
- Temel regexp desteği
- RegExp literals
- RegExp nesnesinin çok temel fonksiyonları
- BigInt literals ve temel BigInt desteği
- equality kontrollerinde veya çeşitli operatörlerin kullanımında daha iyi automatic casting
- arrays, strings gibi temel builtins için daha fazla fonksiyon
Çalıştırma ortamı ve kısıtlar
- Jawsm, görece yeni birkaç WASM önerisini kullandığı için üretilen ikililer henüz runtime’lar arasında yüksek taşınabilirliğe sahip değildir
- Hedef uygulama WASIp2 göz önünde bulundurularak tasarlanıyor
- Wasmtime, components ve WASIp2 çalıştırabilen bir runtime’dır; ancak Jawsm’in kullandığı WASM GC’nin bazı kısımları veya exception handling gibi öğeleri desteklemez
- Runtime’lar standartlaşmış önerileri yakalayana kadar geliştirmeyi kolaylaştırmak için V8 kullanılır
- V8, Chromium veya Node üzerinden kullanılır
- Gerekli WASIp2 özellikleri JavaScript polyfill ile tamamlanır
- Depoda, Jawsm’in ürettiği ikilileri çalıştıran
run.js betiği bulunur
- Nihai hedef, WASM GC, exception handling ve WASIp2 API’lerini uygulayan tüm runtime’larda çalışabilir hale gelmektir
- WASIp2 polyfill kullanma yöntemi de buna dahildir
Kullanım
- Katkı amacı yoksa şu anda kullanılması önerilmez
- Depoyu clone ettikten sonra
execute.sh şu şekilde kullanılabilir
./execute.sh --cargo-run path/to/script.js
- Bu komut bir WAT dosyası üretir, ikiliye derler ve ardından Node.js ile çalıştırır
- Gerekli araçlar şunlardır
- Rust’ın
cargo aracı
- Görece güncel bir
wasm-tools sürümü
- Node.js v23.0.0 veya üzeri
--cargo-run seçeneği verildiğinde, proje önce cargo run ile derlenir ve ardından çalıştırılır
--cargo-run olmadan çalıştırıldığında release build’i çalıştırmaya çalışır; bu yüzden önce cargo build --release çalıştırılmalıdır
İç işleyiş
- Jawsm, JavaScript syntax’ını WASM instructions’a dönüştürür
- Dönüştürme süreci şu WASM önerilerindeki komutlardan yararlanır
-
WASM GC
- exception handling
- tail call optimizations
- Rust kodu script’i dönüştürür; JavaScript semantics’i WASM’e taşımak için bir tip ve fonksiyon kümesi de birlikte kullanılır
- WASM instruction’ların çoğu tarnik kullanılarak üretilir
- tarnik, Rust-like syntax temelinde WASM instructions üreten bir Rust macro’sudur
scope ve closure işleme örneği
- WASM, function references, structs ve arrays destekler; ancak JavaScript’in scope semantics’ini doğrudan sağlamaz
- Jawsm, JavaScript scope davranışını taklit etmek için ek WASM kodu üretir
- Örnek JavaScript kodu şöyledir
let a = "foo";
function bar() {
console.log(a);
}
bar();
- JavaScript’te fonksiyon tanımı, tanımlandığı scope’u miras aldığı için
bar() fonksiyonunun a değişkenine erişebilmesi gerekir
- Dönüştürme akışı kabaca şöyledir
- parent’ı olmayan bir global scope oluşturulur
- Geçerli scope’ta
a değişkeni "foo" olarak bildirilir
bar fonksiyon nesnesi oluşturulurken, fonksiyonun tanımlandığı scope referansı da birlikte saklanır
- Fonksiyon içinde çalıştırma sırasında yeni bir scope oluşturulur, ancak parentScope referansı korunur
retrieve(scope, "a"), geçerli scope’ta ve tüm parent scope’larda a değerini arar
- Geçerli scope’tan
bar alınır ve çağrılır
Lisans
- Kod Apache 2.0 license ile dağıtılır
1 yorum
Hacker News yorumları
WASM GC önerisini gerçekten zekice kullanmış
Şimdiye kadar JS→WASM derleyicileri fiilen tüm JS motorunu beraberinde taşıma yoluna gidiyordu; JS yapısını doğrudan WASM’ın yerel özelliklerine eşlemeye çalışan bir şeyi ilk kez görüyorum
Jaws ile karşılaştırmak ilginç olabilir
Eskiden neredeyse TypeScript’e yakın bir dili gömülü ARM derleyicisi olarak yapmıştım
AssemblyScript’ten çok daha fazla TypeScript’e yakındı; o zaman kullandığımız tekniklerin bazıları yardımcı olabilir
https://www.microsoft.com/en-us/research/uploads/prod/2019/09/static-typescript-draft2.pdf
“Rust kullanmayı gerçekten seviyorum ama yaygın olarak popüler bir dil olmadığını da biliyorum” sözü doğru mu?
Rust aşırı derecede abartılıyor ve bugünlerde her yerde kullanılıyormuş gibi görünüyor
Benim ülkem Estonya’da, insanların kullandığı yerel iş ilanı panolarına göre bölgede 0 iş var; bu yüzden pratikte önemli olan anlamda hiç de popüler sayılmaz
“Yaygın”ı nasıl tanımladığına bağlı ama StackOverflow, PyPL gibi endeksler ve GitHub istatistiklerine bakınca Rust kullanımı JavaScript veya Python’ın yaklaşık %5~10’u gibi görünüyor
“Sonunda JavaScript belirtiminin %100’ünü kapsayabileceğimizden oldukça eminim” deniyorsa test262_runner.rb sonuçları var mı?
test262’yi Porffor yazarının sunumundan öğrendim; README’de https://github.com/CanadaHonk/porffor?tab=readme-ov-file#test262 gibi bir ilerleme göstergesi olsa güzel olurdu
Güzel proje
Şu anda testlerin yaklaşık %12’sini geçiyor, ama uygulanması kolay çok sayıda bölüm hâlâ duruyor
Özellikle projeye başlayalı sadece 2 hafta olduğu düşünülürse bu daha da geçerli; elbette bu, %100’e doğrusal biçimde gidilebileceği anlamına gelmiyor
Yerleşik tiplerin ve fonksiyonların uzun kuyruğu hâlâ var, ama “zor kısımlardan” başlayarak uyguladığım için kolay kısımların bir bölümünü henüz yapmadım
Örneğin test262 harness’ını çalıştırmak için gereken koşul ifadeleri ve while döngüleri kadar sözdizimi uyguladım; for, for in, for of, do while ve switch gibi koşullu yapılar henüz uygulanmadı
Bunlar mevcut if/else ve while uygulamalarına oldukça benzer şekilde eklenebilir
awaitve generator uygulamasını bitirince, son zorlu semantik kavramlar çözülmüş olacak; ardından bu kolay kısımları uygulamayı planlıyorumKapsamanın ne kadar artacağını söylemek zor, ama örneğin şu anda
object["foo"]sözdizimi uygulanmadığı için 1200 test başarısız oluyorobject.fooçalışıyor amaobject["foo"]çalışmıyorBu 1200 testin otomatik olarak hepsinin geçeceği anlamına gelmiyor, ama bu tür nispeten basit sözdizimi eksikleri yüzünden yüzlerce testin başarısız olduğu çok oluyor
Porffor’daki gibi havalı bir grafiği de kesinlikle eklemek istiyorum
Projenin README.md dosyasını okusam da hâlâ tam anlayamadım: beklenen kullanım şekli nedir?
Çıktı olarak üretilen WASM kodunun hangi runtime ile nasıl etkileşime girdiğini merak ediyorum
Hem tarayıcılarla hem de diğer WASM runtime’larıyla uyumlu bir araç mı, yoksa yalnızca projeye bağlanan runtime’da mı çalışıyor, onu da merak ediyorum
Bununla bağlantılı olarak, JavaScript kodu içinde web API’leri veya yalnızca belirli ortamlarda tanımlı global tanımlayıcılar, örneğin modern tarayıcıların ya da Node.js’in global tanımlayıcılarıyla karşılaşınca nasıl tepki veriyor?
Böyle bir ortam hedeflenmiyorsa I/O nasıl yapılmalı?
Güzel sorular; README’ye daha ayrıntılı olarak ekleyeceğim
Bu proje esas olarak sunucuda WebAssembly kullanımını hedefliyor
JavaScript içinde WebAssembly ile JavaScript çalıştırmanın pek anlamlı olmadığını düşünüyorum, ama zamanla frontend eklentilerini sandbox’a almak için yararlı olabilir
İster tarayıcıda çalıştırın ister WasmTime ya da WasmEdge gibi backend runtime’larında, şu anda WebAssembly içinde JavaScript çalıştırmak ideal değil
V8 veya SpiderMonkey gibi JS motorlarını WASM’a derleyip betikleri bunun üzerinde çalıştırmanız ya da AssemblyScript gibi “neredeyse JavaScript” bir dille yetinmeniz gerekiyor
Bu da sunucu iş yüklerini çalıştırmada sınırlayıcı bir faktör oluyor
Örneğin Fastly, WASM worker’larında SpiderMonkey kullanıyor; sadece hello world bile örnek başına 5~10MB bellek kullanıyor
Buna karşılık Shopify, mağazaların sunucu tarafı özelleştirmeleri için WASM kullanırken WASM ikililerini 250KB altında sınırladı; bu boyuta herhangi bir yorumlayıcı sığdırmak zor
Bu yüzden “önerilen” dil AssemblyScript oldu; nedeni burada açıklanıyor: https://shopify.engineering/shopify-webassembly
Bu durum tarihsel olarak WASM’ın çok basit bir runtime olmasından kaynaklanıyor
C kodunu makine koduna derler gibi WASM’a derlemek nispeten kolaydı; ancak WebAssembly’nin kendisi bir tür yorumlayıcı olsa da, onun üzerinde daha yüksek seviyeli bir dili yorumlamak kolay değildi
Artık garbage collection desteği veya istisna işleme desteği gibi yeni öneriler standartlaştıkça, WebAssembly struct, array, function reference gibi özelliklere sahip çok daha güçlü bir yorumlayıcı hâline geliyor
Jaws, bu noktadan yararlanarak JS kodunu WASM koduna dönüştürüyor ve ortaya çıkan kodu SpiderMonkey gibi bir JS motoru olmadan WASM’ın yorumlamasını sağlıyor
Pratikte Jaws’ın ürettiği binary muhtemelen 50KB’ın altında olabilir; bu, SpiderMonkey’i WASM’a derleyip script’leri onun üzerinde çalıştırma yöntemindeki 10MB ile karşılaştırılıyor
Bellek kullanımı da ciddi ölçüde azalacaktır
Fastly gibi şirketler için bu, bellek kullanımını ve sunucu maliyetlerini basamak düzeyinde azaltabilmek anlamına geliyor; Shopify gibi şirketler içinse mevcut JavaScript kodunu, örneğin NPM paketlerini ve JavaScript ekosistemini backend eklenti yazarlarının kullanmasına olanak tanımak demek
“Tarayıcı runtime’ı olmadan JS çalıştırma” yaklaşıyor gibi geliyor
Porffor, Jaws veya başka projelerden birinin eninde sonunda başarılı olacağını düşünüyorum
Bu yaklaşımı gerçekten seviyorum
Binary’yi doğrudan üretmeye çalışmak yerine doğrudan WASM’ı hedefleyerek build edince, WASM GC’ye ve WASI 0.3’e dahil edilmesi beklenen async desteğine dayanabiliyorsun
String encoding farklarını ve ilgili utility’leri nasıl ele alıyor?
Benim kabaca anladığım kadarıyla WASM UTF-8 destekliyor, JS ise potansiyel olarak bozuk UTF-16’yı bile destekliyor
CPU instruction set’leri gibi, WASM abstract machine’de string veya encoding kavramı yok
Linear memory içindeki byte’lardan ibaret; istediğin encoding’i kendin implement edebilirsin
WASM dosya formatında isim encoding’i olarak UTF-8’i belirtir, ama bunun runtime virtual machine ile ilgisi yok
Bazıları buna compiler da diyor
Her hâlükârda iyi iş çıkarmışlar
Bu, aynı kodu doğrudan JS olarak çalıştırmaktan daha hızlı mı, yoksa başka dillerle birlikte çalışabilirlik için mi?
Modern JavaScript compiler’ları, sık çalıştırılan yolları JIT ile oldukça iyi optimize ediyor
Bu projenin kullanım amacı, JavaScript’i WebAssembly sandbox ortamında çalıştırabilmek
Örneğin Shopify, backend kodunu WebAssembly ile genişletmeye izin veriyor ama binary boyutunu 250KB ile sınırlıyor
Bu boyutta şu anda JavaScript kullanmak zor; çünkü QuickJS gibi basit bir interpreter bile WASM’a derlendiğinde birkaç MB oluyor