2 puan yazan GN⁺ 2024-11-11 | 1 yorum | WhatsApp'ta paylaş
  • 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

 
GN⁺ 2024-11-11
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

    • Porffor https://porffor.dev/ ve Static Hermes https://hermesengine.dev/ de derleme yaklaşımını benimsiyor gibi görünüyor
      Jaws ile karşılaştırmak ilginç olabilir
    • Doğru. Ancak dürüst olmak gerekirse, WASM üzerinde tam bir yorumlayıcı yazacak kadar akıllı olmadığım için ortaya çıkan zekice bir kullanım gibi
  • 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

    • Henüz kripto para ile ilgili olmayan bir Rust işi bulamadım
      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
    • Sosyal medyada abartılması ve çok görünür olması, mutlaka yaygın kullanıldığı anlamına gelmez
      “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

      await ve generator uygulamasını bitirince, son zorlu semantik kavramlar çözülmüş olacak; ardından bu kolay kısımları uygulamayı planlıyorum
      Kapsamanı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 oluyor
      object.foo çalışıyor ama object["foo"] çalışmıyor
      Bu 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

Projenin kullandığı runtime **yalnızca WebAssembly**  
Üretilen kod genel olarak bu dosyadaki yaklaşık 3 bin satırlık WAT kodundan [https://github.com/drogus/jaws/blob/main/src/wat/template.wat](<https://github.com/drogus/jaws/blob/main/src/wat/template.wat>;) ve kullanıcının JS kodunun dönüştürülmüş kısmından oluşuyor  
Örneğin `"console.log('foo')"` gibi çok basit bir programda tüm “üretilen” bölüm yalnızca şundan ibaret: [https://gist.github.com/drogus/1c49c25ed0b14804b2f27e10d2a79928/…](<https://gist.github.com/drogus/1c49c25ed0b14804b2f27e10d2a79928/…;)  
Bu, kabaca argümanı `new_static_string` ile hazırladıktan sonra `console.log` çağırıyor  
Şimdilik host tarafında az miktarda glue code gerekiyor, ancak sonunda WASIp2, WASM GC ve exception handling önerisini destekleyen herhangi bir runtime’da bu tür binary’leri çalıştırmak mümkün olacak

Web API’leri veya ortama özgü global identifier desteği henüz uygulanmadı, ancak nasıl çalışacağını söylemek mümkün  
**Node.js API**’lerinin WASI üzerinden desteklenmesi planlanıyor  
WASI, WASM programları ile dış dünya arasında iletişim kurmak için bir standart  
Örneğin HTTP isteği gönderme, STDOUT’a yazma, dosya okuma/yazma gibi işler için kullanılabilecek standart bir fonksiyon kümesi tanımlar  
Bu yüzden `fetch` veya `fs` gibi API’lere gelindiğinde, WASI preview2 destekleyen runtime’larda çalışması gerekir  
Tarayıcılar da polyfill ile destekleyebilir, ancak bu durumda I/O desteği daha özelleştirilmiş olur  
Bir WASM programının dosya okumasına veya yazmasına izin veriyorsanız, örneğin localStorage’a kaydetmek, WASM’a derlenmiş bir SQLite veritabanı kullanmak ya da hatta S3 gibi bir yere göndermek gibi bir mekanizma sağlamanız gerekir
  • “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

    • Başlığı yazarken ifadenin biraz tuhaf olduğunu hiç fark etmemişim
  • Bu, aynı kodu doğrudan JS olarak çalıştırmaktan daha hızlı mı, yoksa başka dillerle birlikte çalışabilirlik için mi?

    • Bu aşamada söylemek zor, ama JIT etkin SpiderMonkey veya V8’den daha hızlı olma ihtimalinin çok düşük olduğunu düşünüyorum
      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