2 poin oleh GN⁺ 2024-07-31 | 1 komentar | Bagikan ke WhatsApp
  • 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 eval sulit 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

 
GN⁺ 2024-07-31
Pendapat di Hacker News
  • Oliver, pengembang utama Porffor, mengumumkan bahwa ia akan bekerja penuh waktu di Porffor: https://x.com/canadahonk/status/1818347311417938237

  • 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 Int dan Float bisa disediakan, lalu diturunkan menjadi Number saat diperlukan, sambil tetap disimpan di register
    Masalah 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

    • Tanpa ekstensi tambahan, TypeScript tidak banyak membantu seperti yang dibayangkan. Sebab sejak awal TypeScript memang tidak dirancang untuk tujuan seperti itu
      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
    • Sebagai kontributor Porffor, saya tidak setuju. JavaScript juga punya cukup banyak ruang untuk diperbaiki pada waktu kompilasi
      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
    • Proyek yang cukup terkait dengan ide ini adalah AssemblyScript: https://www.assemblyscript.org
    • ECMAScript 4 adalah upaya untuk menambahkan tipe yang lebih baik ke bahasa ini, tetapi sayangnya gagal sejak lama
      Akan menyenangkan jika TypeScript setidaknya bisa menentukan tipe seperti integer. Dalam kompilasi TS→JS biasa, const val: int mungkin diperlakukan sama persis dengan const val: number, tetapi runtime modern yang memahami TS bisa memanfaatkan informasi tambahan itu
      Saya penasaran apakah sintaks seperti const counter: Number bisa diterima
    • Setelah mengatakan “saya pernah memikirkan ini, tetapi sulit mendapatkan performa lebih baik”, pendekatan yang dibicarakan justru meminta hal-hal yang sudah dijelaskan tepat di bagian atas halaman depan situs
      Entah 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

    • Sebagai pengembang, saya setuju. Ini terlihat seperti kasus penggunaan menarik yang berpotensi dibantu Porffor. Akan menyenangkan jika suatu saat bisa membicarakannya
  • 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 prakompilasi
    Secara 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

    • Sebagai catatan, Static Hermes sepenuhnya mendukung kemampuan mengompilasi JS ke WASM. Karena sudah memiliki backend LLVM, fitur ini didapat hampir gratis. Contohnya bisa dilihat di https://x.com/tmikov/status/1706138872412074204
      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
    • Sebagai kontributor Porffor, menurut saya ini perbandingan yang bagus. Namun Porffor juga secara teknis mendukung promise. Hanya saja ia berjalan secara sinkron
      Pendekatannya mirip Kiesel: https://kiesel.dev/
    • Ada beberapa koreksi kecil. Porffor belum sepenuhnya self-hosted, tetapi kami berharap itu memungkinkan. Namun fitur bawaan seperti Array.prototype.filter, Math.sin, dan atob sebagian mengompilasi dirinya sendiri
      Belakangan ini Porffor juga mulai mendukung async/promise/await dasar. Masih belum terlalu baik
    • Sepertinya ketergantungan pada LLVM terdengar seperti hal yang buruk
  • 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.blink didukung. Pengembang yang punya humor dan sisi jahil selalu merupakan tanda yang baik

    • Jika tujuannya adalah “host ECMAScript berperilaku seperti browser web”, maka tentu saja harus didukung. Itu bagian dari spesifikasi: https://tc39.es/ecma262/multipage/additional-ecmascript-feat...
      Implementasinya 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.blink ada di test262, jadi untuk mencapai tujuan proyek, pada dasarnya memang harus didukung
  • Saya 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

    • Sudah ada proyek-proyek yang melakukan JS-to-WASM dengan membundel interpreter JS. Jadi kemungkinan ungkapan itu dipakai untuk memperjelas perbedaannya dari pendekatan semacam itu
  • 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...

    • Mungkin maksudnya pekerjaan yang menyebabkan regresi Test262 bersifat sementara dan dilakukan di branch terpisah, lalu baru digabungkan ke main hanya setelah semua perbaikan yang diperlukan untuk menghilangkan regresi ikut disertakan. Nomor versi baru cukup dipakai setelah merge itu terjadi
  • Dalam bahasa Welsh, artinya “ungu

    • Etimologinya berasal dari bahasa Yunani yang berarti ungu, dan kata bahasa Inggris dengan akar yang sama yang mungkin paling umum adalah porphyry, mineral berwarna 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