2 poin oleh GN⁺ 2024-11-11 | 1 komentar | Bagikan ke WhatsApp
  • Jawsm adalah kompilator JavaScript→WebAssembly yang ditulis dalam Rust, sebuah alat eksperimental untuk membuat biner WASM mandiri yang dapat dijalankan tanpa interpreter
  • Mirip dengan porffor dalam menghasilkan WASM standalone, tetapi pendekatan implementasinya berbeda; ia mengubah sintaks JavaScript menjadi instruksi WASM dengan memanfaatkan instruksi dari proposal WASM GC, exception handling, dan optimasi tail call terbaru
  • Saat ini berhasil melewati sekitar 25% dari test suite test262, dan scopes/closures, try/catch, async/await, serta generators yang dianggap penting untuk memverifikasi kelayakan sudah diimplementasikan
  • Belum siap untuk produksi; banyak fitur bahasa dan tipe bawaan yang masih hilang atau belum lengkap, dan RegExp, sebagian besar builtins, serta arithmetic BigInt juga belum tersedia
  • Biner yang dihasilkan memiliki portabilitas antarruntime yang rendah karena bergantung pada proposal WASM terbaru, dan saat ini dijalankan di Chromium atau Node berbasis V8 bersama polyfill WASIp2

Tujuan dan posisi Jawsm

  • Jawsm adalah kompilator JavaScript ke WebAssembly yang diucapkan seperti “awesome”
  • Ditulis dalam Rust dan bertujuan membuat standalone WASM binary yang dapat menjalankan kode JavaScript tanpa interpreter
  • Menghasilkan keluaran yang mirip dengan porffor, tetapi pendekatan implementasinya berbeda
  • Saat ini masih merupakan alat eksperimental dan belum siap digunakan di produksi
    • Banyak fitur bahasa JavaScript yang masih hilang atau belum lengkap
    • Banyak tipe dan metode bawaan juga masih hilang atau belum lengkap
  • Tujuan jangka panjangnya adalah mendukung 100% fitur bahasa JavaScript

Mengapa Jawsm dibuat

  • Proyek ini dimulai saat mengerjakan Crows, alat stress test untuk menjalankan skenario WebAssembly
  • Crows saat ini hanya mendukung kode yang dikompilasi dari Rust ke WASM
  • Pengujian kecil sering kali lebih mudah ditulis dalam interpreted language, tetapi menjalankan scripting language di atas WASM saat ini belum ideal
    • Jika menyertakan interpreter, ukuran biner menjadi setidaknya beberapa MB dan penggunaan memorinya juga lebih besar
    • Atau harus menggunakan varian bahasa target seperti TinyGo atau AssemblyScript
  • Dengan memanfaatkan proposal WASM modern, tujuannya adalah memungkinkan implementasi 100% fitur JavaScript tanpa interpreter yang telah dikompilasi
  • Ini berangkat dari asumsi bahwa runtime WASM itu sendiri sudah merupakan interpreter

Fitur yang saat ini sudah berfungsi

  • Jawsm saat ini melewati sekitar 25% dari test suite test262
  • Keempat fitur inti untuk memverifikasi kelayakan proyek semuanya sudah diimplementasikan
    • scopes/closures
    • try/catch
    • async/await
    • generators
  • Fitur lain yang seharusnya sudah berfungsi adalah sebagai berikut
    • deklarasi dan assignment var, let, const
    • loop do..while, while, for, for..in, for..of
    • statement switch
    • dukungan terbatas untuk break, continue
    • string literal dan penjumlahan string literal
    • angka dan operator dasar +, -, *, /
    • boolean dan operator boolean dasar
    • array dan sebagian besar fungsi terkait Array
    • object literals
    • keyword new
    • async, await
    • dukungan terbatas untuk API Promise
    • generator functions
    • try/catch
    • dukungan BigInt yang sangat dasar

Fitur yang masih belum ada

  • Item utama yang saat ini belum tersedia adalah sebagai berikut
    • sebagian besar builtins
    • sebagian besar metode dari builtins yang sudah ada
    • ekspresi RegExp
    • arithmetic BigInt
  • Rencana langkah berikutnya berfokus pada implementasi fitur-fitur berikut
    • dukungan regexp dasar
      • literal RegExp
      • fungsi yang sangat dasar pada objek RegExp
    • literal BigInt dan dukungan dasar BigInt
    • automatic casting yang lebih baik saat melakukan pemeriksaan equality atau menggunakan berbagai operator
    • lebih banyak fungsi untuk builtins dasar seperti arrays, strings, dan lain-lain

Lingkungan eksekusi dan keterbatasan

  • Karena Jawsm menggunakan beberapa proposal WASM yang relatif baru, biner yang dihasilkan masih belum memiliki portabilitas tinggi antar runtime
  • Implementasi targetnya dirancang dengan WASIp2 sebagai acuan
  • Wasmtime adalah runtime yang dapat menjalankan components dan WASIp2, tetapi belum mendukung beberapa bagian WASM GC atau hal seperti exception handling yang digunakan Jawsm
  • Untuk mempermudah pengembangan sampai runtime mengejar proposal yang telah distandardisasi, proyek ini menggunakan V8
    • V8 digunakan melalui Chromium atau Node
    • Fitur WASIp2 yang diperlukan dilengkapi dengan polyfill JavaScript
  • Repositorinya menyertakan skrip run.js untuk menjalankan biner yang dihasilkan Jawsm
  • Pada akhirnya, targetnya adalah dapat berjalan di semua runtime yang mengimplementasikan WASM GC, exception handling, dan API WASIp2
    • termasuk juga pendekatan menggunakan polyfill WASIp2

Cara penggunaan

  • Jika bukan untuk tujuan kontribusi, saat ini penggunaannya belum direkomendasikan
  • Setelah clone repositori, Anda dapat menggunakan execute.sh seperti berikut
./execute.sh --cargo-run path/to/script.js
  • Perintah ini akan membuat file WAT, mengompilasikannya menjadi biner, lalu menjalankannya dengan Node.js
  • Alat yang diperlukan adalah sebagai berikut
    • cargo dari Rust
    • versi wasm-tools yang relatif baru
    • Node.js v23.0.0 atau lebih baru
  • Jika opsi --cargo-run diberikan, proyek akan dikompilasi terlebih dahulu dengan cargo run lalu dijalankan
  • Jika dijalankan tanpa --cargo-run, ia akan mencoba menjalankan release build, sehingga Anda harus lebih dulu menjalankan cargo build --release

Cara kerja internal

  • Jawsm mengubah sintaks JavaScript menjadi instruksi WASM
  • Proses transformasi memanfaatkan instruksi dari proposal WASM berikut
    • WASM GC

      • exception handling
      • tail call optimizations
      • Kode Rust mentransformasikan skrip, dan bersama itu digunakan kumpulan tipe serta fungsi untuk memindahkan semantik JavaScript ke WASM
      • Sebagian besar instruksi WASM dihasilkan menggunakan tarnik
      • tarnik adalah macro Rust yang menghasilkan instruksi WASM berdasarkan sintaks mirip Rust

Contoh penanganan scope dan closure

  • WASM mendukung function references, structs, dan arrays, tetapi tidak secara langsung menyediakan semantik scope JavaScript
  • Jawsm menghasilkan kode WASM tambahan untuk meniru perilaku scope JavaScript
  • Contoh kode JavaScriptnya adalah sebagai berikut
let a = "foo";

function bar() {
  console.log(a);
}

bar();
  • Dalam JavaScript, definisi fungsi mewarisi scope tempat ia didefinisikan, jadi bar() harus bisa mengakses variabel a
  • Alur transformasinya secara garis besar adalah sebagai berikut
    • membuat global scope tanpa parent
    • mendeklarasikan variabel a dengan nilai "foo" pada scope saat ini
    • saat membuat objek fungsi bar, referensi ke scope tempat fungsi didefinisikan juga disimpan
    • saat fungsi dijalankan, scope baru dibuat sambil tetap mempertahankan referensi parentScope
    • retrieve(scope, "a") mencari a di scope saat ini dan semua parent scope
    • mengambil bar dari scope saat ini lalu memanggilnya

Lisensi

  • Kode didistribusikan di bawah lisensi Apache 2.0

1 komentar

 
GN⁺ 2024-11-11
Pendapat di Hacker News
  • Pemanfaatan proposal WASM GC yang benar-benar cerdas
    Hingga sekarang, compiler JS→WASM pada dasarnya memuat seluruh engine JS, tetapi ini pertama kalinya saya melihat upaya memetakan struktur JS langsung ke fitur native WASM

    • Porffor https://porffor.dev/ dan Static Hermes https://hermesengine.dev/ juga tampaknya mengambil pendekatan kompilasi
      Akan menarik jika dibandingkan dengan Jaws
    • Benar. Namun sejujurnya, ini lebih dekat ke pemanfaatan cerdas yang muncul karena tidak cukup pintar untuk menulis seluruh interpreter di atas WASM
  • Dulu saya pernah membuat bahasa yang hampir mendekati TypeScript sebagai compiler ARM embedded
    Bahasa itu jauh lebih dekat ke TypeScript daripada AssemblyScript, dan sebagian teknik yang dipakai saat itu mungkin bisa membantu
    https://www.microsoft.com/en-us/research/uploads/prod/2019/09/static-typescript-draft2.pdf

  • Apakah pernyataan “Saya sangat suka memakai Rust, tetapi saya juga tahu itu bukan bahasa yang populer secara luas” benar?
    Rust sangat banyak di-hype dan belakangan tampak dipakai di mana-mana

    • Saya belum menemukan pekerjaan Rust yang tidak terkait kripto
      Di negara saya, Estonia, berdasarkan papan lowongan lokal yang dipakai orang-orang, ada 0 pekerjaan lokal, jadi dalam arti yang benar-benar penting, sulit untuk menyebutnya populer sama sekali
    • Terlihat sering dan di-hype di media sosial tidak selalu berarti dipakai secara luas
      Bergantung pada bagaimana mendefinisikan “luas”, tetapi jika melihat indeks seperti StackOverflow dan PyPL, serta statistik GitHub, penggunaan Rust tampaknya sekitar 5–10% dari JavaScript atau Python
  • Jika “pada akhirnya cukup yakin bisa mencakup 100% spesifikasi JavaScript”, apakah ada hasil test262_runner.rb?
    Saya mengetahui test262 dari presentasi pembuat Porffor, dan akan bagus jika ada indikator progres seperti https://github.com/CanadaHonk/porffor?tab=readme-ov-file#test262 di README
    Proyek yang bagus

    • Saat ini lulus sekitar 12% pengujian, tetapi masih banyak bagian yang mudah diimplementasikan yang tersisa
      Apalagi mengingat proyek ini baru dimulai 2 minggu lalu, dan tentu saja itu tidak berarti progres menuju 100% bisa berjalan linear
      Masih ada ekor panjang tipe dan fungsi bawaan, tetapi karena saya mengimplementasikan “bagian sulit” terlebih dahulu, sebagian bagian mudah belum dikerjakan
      Misalnya, untuk menjalankan harness test262, saya baru mengimplementasikan sintaks sebatas conditional dan while loop yang diperlukan, sementara ekspresi kondisional seperti for, for in, for of, do while, dan switch belum diimplementasikan
      Semua itu bisa ditambahkan dengan cara yang hampir sama seperti implementasi if/else dan while yang sudah ada

      Setelah implementasi await dan generator selesai, konsep semantik sulit terakhir akan terselesaikan, jadi setelah itu saya berencana mengimplementasikan bagian-bagian mudah tersebut
      Sulit mengatakan seberapa besar cakupan akan naik, tetapi misalnya saat ini 1200 pengujian gagal karena sintaks object["foo"] belum diimplementasikan
      object.foo berfungsi, tetapi object["foo"] belum
      Itu bukan berarti 1200 pengujian tersebut otomatis semuanya akan lolos, tetapi sering kali ratusan pengujian gagal karena kekurangan sintaks yang relatif sederhana seperti ini

      Saya juga sangat ingin menambahkan grafik keren seperti Porffor

  • Setelah membaca README.md proyek ini, saya masih belum begitu paham: pola penggunaan yang diharapkan seperti apa?
    Saya penasaran bagaimana kode WASM yang dihasilkan berinteraksi dengan runtime apa
    Saya juga penasaran apakah ini alat yang kompatibel dengan browser maupun runtime WASM lain, atau hanya berjalan pada runtime yang terhubung ke proyek

    Terkait itu, bagaimana reaksinya ketika di dalam kode JavaScript ditemukan Web API atau identifier global yang hanya didefinisikan di lingkungan tertentu, misalnya identifier global di browser modern atau Node.js?
    Jika tidak menargetkan lingkungan seperti itu, bagaimana seharusnya melakukan I/O?

    • Pertanyaan yang bagus, dan saya akan menambahkan penjelasan lebih rinci ke README
      Proyek ini terutama menargetkan penggunaan WebAssembly di server
      Menjalankan JavaScript di atas WebAssembly dari dalam JavaScript menurut saya tidak terlalu bermakna, tetapi seiring waktu mungkin berguna untuk sandboxing plugin frontend

      Baik dijalankan di browser maupun di runtime backend seperti WasmTime atau WasmEdge, menjalankan JavaScript di dalam WebAssembly saat ini belum ideal
      Anda harus mengompilasi engine JS seperti V8 atau SpiderMonkey ke WASM lalu menjalankan skrip di atasnya, atau puas dengan bahasa “hampir JavaScript” seperti AssemblyScript
      Ini menjadi faktor pembatas untuk menjalankan workload server
      Misalnya, Fastly memakai SpiderMonkey untuk worker WASM, dan bahkan hello world saja memakai 5–10MB memori per instance
      Sebaliknya, Shopify memakai WASM untuk kustomisasi sisi server toko dan membatasi binary WASM hingga di bawah 250KB, dan pada ukuran ini sulit memasukkan interpreter apa pun
      Karena itu, bahasa yang “direkomendasikan” menjadi AssemblyScript, dan alasannya dijelaskan di sini: https://shopify.engineering/shopify-webassembly

      Situasi ini secara historis terjadi karena WASM merupakan runtime yang sangat sederhana
      Mengompilasi kode C ke WASM seperti mengompilasinya ke machine code relatif mudah, tetapi meskipun WebAssembly sendiri semacam interpreter, tidak mudah menginterpretasikan bahasa yang lebih tinggi di atasnya

      Kini, seiring proposal baru seperti dukungan garbage collection atau dukungan exception handling distandardisasi, WebAssembly menjadi interpreter yang jauh lebih kuat dengan fitur seperti struct, array, dan function reference

Jaws memanfaatkan hal ini dengan mengubah kode JS menjadi kode WASM, lalu membuat WASM mengeksekusi kode hasilnya tanpa mesin JS seperti SpiderMonkey
Dalam praktiknya, biner yang dihasilkan Jaws mungkin di bawah 50KB, dibandingkan dengan 10MB pada pendekatan mengompilasi SpiderMonkey ke WASM lalu menjalankan skrip di atasnya
Penggunaan memorinya juga akan jauh lebih rendah
Bagi perusahaan seperti Fastly, ini berarti penggunaan memori dan biaya server bisa dipangkas hingga beberapa digit; bagi perusahaan seperti Shopify, ini berarti mereka bisa memungkinkan penulis plugin backend memanfaatkan kode JavaScript yang sudah ada, misalnya paket NPM dan ekosistem JavaScript

Runtime yang digunakan proyek ini **hanya WebAssembly**  
Kode yang dihasilkan umumnya terdiri dari sekitar 3 ribu baris kode WAT di berkas ini [https://github.com/drogus/jaws/blob/main/src/wat/template.wat](<https://github.com/drogus/jaws/blob/main/src/wat/template.wat>;) dan bagian kode JS pengguna yang telah dikonversi  
Misalnya, pada program yang sangat sederhana seperti `"console.log('foo')"`, seluruh bagian “yang dihasilkan” hanya ini: [https://gist.github.com/drogus/1c49c25ed0b14804b2f27e10d2a79928/…](<https://gist.github.com/drogus/1c49c25ed0b14804b2f27e10d2a79928/…;)  
Ini kurang lebih menyiapkan argumen dengan `new_static_string`, lalu memanggil `console.log`  
Saat ini masih diperlukan sedikit glue code di sisi host, tetapi pada akhirnya biner seperti ini akan bisa dijalankan di runtime mana pun yang mendukung WASIp2, WASM GC, dan proposal exception handling

Dukungan untuk Web API atau identifier global khusus lingkungan belum diimplementasikan, tetapi cara kerjanya bisa dijelaskan  
**API Node.js** direncanakan akan didukung melalui WASI  
WASI adalah standar agar program WASM dan dunia luar dapat berkomunikasi  
Misalnya, WASI mendefinisikan kumpulan fungsi standar yang bisa digunakan untuk mengirim permintaan HTTP, menulis ke STDOUT, membaca/menulis berkas, dan sebagainya  
Jadi saat mencapai API seperti `fetch` atau `fs`, itu seharusnya berjalan pada runtime yang mendukung WASI preview2  
Browser juga bisa didukung dengan polyfill, tetapi dalam kasus ini dukungan I/O menjadi lebih khusus  
Jika program WASM diizinkan membaca atau menulis berkas, misalnya, Anda harus menyediakan mekanisme seperti menyimpannya ke localStorage, memakai database SQLite yang dikompilasi ke WASM, atau bahkan mengirimkannya ke tempat seperti S3
  • Rasanya “menjalankan JS tanpa runtime browser” sudah semakin dekat
    Porffor, Jaws, atau salah satu proyek lain sepertinya pada akhirnya akan berhasil

  • Saya sangat suka pendekatan ini
    Alih-alih mencoba menghasilkan biner secara langsung, jika membangun dengan target langsung ke WASM, kita bisa mengandalkan WASM GC dan dukungan async yang tampaknya akan masuk ke WASI 0.3

  • Bagaimana menangani perbedaan encoding string dan utilitas terkait?
    Sejauh pemahaman samar saya, WASM mendukung UTF-8, sementara JS juga mendukung UTF-16 yang berpotensi rusak

    • Seperti set instruksi CPU, mesin abstrak WASM tidak memiliki konsep string atau encoding
      Itu hanya byte di dalam memori linear, dan encoding apa pun yang diinginkan bisa diimplementasikan sendiri

      WASM memang menetapkan UTF-8 sebagai encoding nama dalam format berkasnya, tetapi itu tidak terkait dengan mesin virtual runtime

  • Sebagian orang juga menyebut hal itu sebagai compiler
    Bagaimanapun, ini dibuat dengan baik

    • Saat menulis judul, saya sama sekali tidak menyadari bahwa kata-katanya agak aneh
  • Apakah ini lebih cepat daripada menjalankan kode yang sama langsung sebagai JS, atau tujuannya untuk interoperabilitas dengan bahasa lain?

    • Pada tahap ini sulit dikatakan, tetapi menurut saya kemungkinannya sangat kecil untuk lebih cepat daripada SpiderMonkey atau V8 dengan JIT aktif
      Compiler JavaScript modern cukup baik mengoptimalkan jalur yang sering dijalankan dengan JIT
      Kegunaan proyek ini adalah memungkinkan JavaScript dijalankan di lingkungan sandbox WebAssembly
      Misalnya, Shopify memungkinkan kode backend diperluas dengan WebAssembly, tetapi membatasi ukuran biner menjadi 250KB
      Pada ukuran itu, saat ini sulit memakai JavaScript, karena interpreter sederhana seperti QuickJS pun menjadi beberapa MB saat dikompilasi ke WASM