- Porffor adalah proyek riset yang mengompilasi JavaScript terlebih dahulu, bukan saat dijalankan, menjadi WebAssembly dan biner native
- Dengan pendekatan yang tidak membundel interpreter, Porffor menargetkan output yang 10–30 kali lebih kecil dan lebih cepat dibanding proyek JS→Wasm yang ada
- Pada build native pun, karena tidak memaketkan runtime, ukuran biner bisa berkurang hingga 1000 kali, dengan contoh dari sekitar 90MB menjadi kurang dari 100KB
- Ditulis dalam JS, tanpa
eval, dan mengusung struktur yang mendukung TypeScript secara native tanpa tahap build terpisah - AOT menguntungkan untuk optimasi analisis statis dan kompilasi sebelum eksekusi, tetapi evaluasi JS dinamis seperti
evalsulit dilakukan, dan proyek ini masih berada di tahap awal sehingga banyak JS belum berjalan
Cara eksekusi yang dibuat Porffor
- Porffor adalah proyek riset yang mengompilasi JavaScript secara Ahead-of-Time menjadi WebAssembly dan biner native
- Biner TypeScript yang dikompilasi dengan Porffor menyajikan halaman ini
- Karena sejak awal ditulis dengan mempertimbangkan AOT, strukturnya mencoba optimasi yang sulit dilakukan pada cara eksekusi JS yang ada
Perbedaan output WebAssembly dan native
-
JS → Wasm
- Output WebAssembly dari Porffor 10–30 kali lebih kecil dan lebih cepat dibanding proyek JS→Wasm yang ada
- Perbedaan utamanya adalah Porffor mengompilasi JS secara langsung dan tidak membundel interpreter
- Menjalankan JS di Wasm memungkinkan eksekusi dalam sandbox, tetapi bisa menimbulkan penalti performa yang besar; Porffor berfokus untuk mengurangi biaya ini
- Contoh penerapan:
- Hosting JS sisi server: sandboxing Wasm pada runtime edge dapat menyediakan eksekusi yang aman tanpa isolasi berlebihan
- Overhead AOT yang rendah membuka kemungkinan menjalankan lebih banyak pelanggan pada hardware yang sama dibanding JIT dengan kehilangan performa minimal
- Ketahanan terhadap reverse engineering: untuk JS sensitif, kode yang sudah dikompilasi bisa lebih sulit direkayasa balik dibanding obfuscation
-
JS → Native
- Karena benar-benar mengompilasi JS tanpa memaketkan runtime, ukuran biner bisa menjadi hingga 1000 kali lebih kecil
- Contoh ukurannya sekitar 90MB → kurang dari 100KB
- Secara internal, JS dikompilasi ke C lalu dikompilasi menjadi native, sehingga JS dapat digunakan di tempat-tempat yang bisa menggunakan C
- Contoh penerapan:
- Eksekusi JS cepat di embedded, konsol game, dan sejenisnya
- Aplikasi CLI JS kecil yang dikompilasi menjadi executable sekali klik berukuran di bawah 1MB
Keunggulan dan batasan AOT
- Interpreter tradisional atau beberapa tahap JIT harus menyeimbangkan waktu startup dan performa JS
- AOT mengompilasi lebih dulu dan menjalankan kemudian, sehingga kecepatan kompilasi penting bagi pengalaman developer tetapi tidak memengaruhi pengalaman pengguna
- Pendekatan ini memberi ruang untuk melakukan optimasi berbasis analisis statis seperti pada C++ dan Rust
- Kekurangan utamanya adalah tidak adanya evaluasi JS dinamis seperti
eval, dan perlunya membuat mesin JS baru - Karena masih tahap awal, banyak JS belum berjalan, tetapi pekerjaan perbaikan sedang berlangsung
- Untuk melacak progres kompatibilitas ECMAScript, test suite resmi Test262 dijalankan pada setiap commit
1 komentar
Pendapat di Hacker News
Oliver, pengembang utama Porffor, mengumumkan bahwa ia akan bekerja penuh waktu di Porffor: https://x.com/canadahonk/status/1818347311417938237
https://news.ycombinator.com/user?id=defunkt
Pernah memikirkan hal serupa, tetapi menurut saya sulit mendapatkan performa yang jauh lebih baik dari JavaScript. Mungkin yang terbaik adalah mentranspilasi JS menjadi pemanggilan V8 C++
Optimisasi yang benar-benar keren muncul saat mengompilasi TypeScript atau sesuatu yang mendekatinya. Dengan memanfaatkan tipe, keuntungan besar bisa didapat, dan bagian tanpa tipe pada dasarnya akan jatuh kembali ke pemanggilan JS yang lambat. Interface bisa direduksi menjadi tabel fungsi virtual atau pemanggilan langsung, dan bisa juga bekerja di atas struct alih-alih map. Tipe
IntdanFloatbisa disediakan, lalu diturunkan menjadiNumbersaat diperlukan, sambil tetap disimpan di registerMasalah utamanya adalah TS dan V8 sama-sama target nonstandar yang berubah cepat. Proyek seperti ini membutuhkan tim besar, dan menjaga kompatibilitas itu sendiri menjadi pekerjaan tersendiri
Contoh sederhana: TypeScript tidak membedakan bilangan bulat dan bilangan floating-point; semuanya diperlakukan sebagai angka. Karena itu, setiap akses array memerlukan konversi tipe. Jika TypeScript dirancang untuk membantu kompilasi statis, kemungkinan pembedaan ini akan ada
Masalah yang lebih besar adalah subtyping struktural TypeScript. Karena karakteristik ini, praktis mustahil bagi compiler untuk menentukan secara statis struktur fisik argumen non-primitif yang diteruskan ke fungsi. JIT bisa melakukan analisis bentuk secara dinamis, sehingga performanya bisa lebih buruk daripada JIT pada setiap akses field
Sudah banyak upaya membuat alat analisis tipe statis untuk JS, dan analisis yang sangat menyeluruh pun memungkinkan. Contoh yang terlintas, meski agak lama, adalah TAJS
Akan menyenangkan jika TypeScript setidaknya bisa menentukan tipe seperti
integer. Dalam kompilasi TS→JS biasa,const val: intmungkin diperlakukan sama persis denganconst val: number, tetapi runtime modern yang memahami TS bisa memanfaatkan informasi tambahan ituSaya penasaran apakah sintaks seperti
const counter: Numberbisa diterimaEntah situsnya berubah, atau saya yang melewatkan sesuatu
Di windmill.dev, saat pengguna men-deploy kode, mereka menggunakan Bun build untuk membundel skrip dan semua dependensi menjadi satu file JS, lalu memuatnya untuk memperbaiki cold start dan penggunaan memori. Karena ukuran bundel, hasilnya disimpan di S3
Jika semuanya bisa dibundel secara native, permainannya benar-benar berubah. Sebagus apa pun cold start Bun, sulit mengalahkan eksekusi native langsung dari binary kecil
Senang melihat makin banyak runtime JS mendekati Wasm. Proyek ini mengingatkan pada Static Hermes, engine JS milik Facebook untuk mempercepat proyek React Native di iOS dan Android
Keduanya menargetkan kepatuhan JS test262, dan sementara Porffor mendukung output native maupun Wasm, Static Hermes saat ini terutama berfokus pada output native. Porffor ditulis dalam JS murni dan mengarah untuk bisa mengompilasi dirinya sendiri, sedangkan Static Hermes bergantung pada LLVM. Dukungan async/promise/await di Porffor masih terbatas, sementara Static Hermes mendukungnya dengan beberapa batasan. Static Hermes ditulis dalam C++, sedangkan Porffor terutama ditulis dalam JS. Keduanya mendukung TypeScript, tetapi Static Hermes mentranspilasi TS AST ke Flow, sementara Porffor mendukungnya secara native. Static Hermes memiliki interpreter cadangan untuk situasi JS yang sulit dikompilasi seperti
eval, sedangkan Porffor hanya mendukung prakompilasiSecara keseluruhan, saya berharap proyek ini mendapatkan momentum dan bisa membuat engine JavaScript di edge menjadi lebih cepat. Ditulis sebagai Syrus dari Wasmer
https://github.com/facebook/hermes/discussions/1137
https://github.com/tc39/test262
https://wasmer.io
Namun itu bukan fokus kami; kami terutama berfokus pada React Native. Dalam lingkungan itu, WASM tidak terlalu berarti
Fitur terpenting Static Hermes adalah type checker yang menjamin soundness runtime. Porffor sangat menarik, saya sudah mengikutinya cukup lama dan mendukung agar proyek ini berhasil
Pendekatannya mirip Kiesel: https://kiesel.dev/
Array.prototype.filter,Math.sin, danatobsebagian mengompilasi dirinya sendiriBelakangan ini Porffor juga mulai mendukung async/promise/await dasar. Masih belum terlalu baik
JavaScript memiliki subset yang mudah dikompilasi, dan yang sulit adalah long tail di luar itu. Meski begitu, keren melihat penelitian berjalan untuk mengetahui di mana batasnya dan seberapa besar keuntungan yang bisa didapat dari subset tersebut
Saya benar-benar suka bahwa
String.blinkdidukung. Pengembang yang punya humor dan sisi jahil selalu merupakan tanda yang baikImplementasinya juga sepele, kira-kira setingkat
function() { return "" + this + ""; }, jadi layak diimplementasikan meskipun host ECMAScript bukan browser web. Dalam kasus seperti itu, sifatnya opsional. Saya tidak berharap ini ada kaitannya dengan “humor atau sisi jahil”String.blinkada di test262, jadi untuk mencapai tujuan proyek, pada dasarnya memang harus didukungSaya penasaran nuansa halus apa yang saya lewatkan. Saya tidak tahu mengapa “engine JS prakompilasi” adalah deskripsi yang lebih baik daripada “kompiler JS-to-Wasm”. Kalau ini terutama strategi framing, itu juga tidak masalah
Skema versi yang dijelaskan di sini agak meragukan
Jika suatu perubahan menyebabkan regresi pada sebagian tes Test262, nomor versi juga bisa ikut mundur. Artinya, Porffor tidak bisa sekaligus memiliki nomor versi yang meningkat monoton dan kemampuan melakukan perubahan yang diperlukan tetapi dapat menyebabkan regresi Test262
https://github.com/CanadaHonk/porffor?tab=readme-ov-file#ver...
Dalam bahasa Welsh, artinya “ungu”
Menyegarkan melihat beberapa engine JS bermunculan untuk berbagai tujuan
Untuk menyematkan plugin dalam aplikasi, saya telah mengerjakan penyediaan lebih banyak API kompatibel Node untuk quickjs melalui llrt
https://github.com/awslabs/llrt