Pengumpulan Sampah WebAssembly (WasmGC) kini diaktifkan secara default di Chrome

Ada dua jenis bahasa pemrograman: bahasa pemrograman dengan pengumpulan sampah dan bahasa pemrograman yang memerlukan pengelolaan memori manual. Contoh yang pertama, di antara banyak lagi, adalah Kotlin, PHP, atau Java. Contohnya adalah C, C++, atau Rust. Sebagai aturan umum, bahasa pemrograman tingkat yang lebih tinggi cenderung memiliki pengumpulan sampah sebagai fitur standar. Dalam postingan blog ini, fokusnya adalah pada bahasa pemrograman yang dikumpulkan sampah dan cara mengompilasinya ke WebAssembly (Wasm). Namun, apa itu pengumpulan sampah (sering disebut GC) pada awalnya?

Browser Support

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: 18.2.

Pembersihan sampah memori

Secara sederhana, ide pengumpulan sampah adalah upaya untuk mendapatkan kembali memori yang dialokasikan oleh program, tetapi tidak lagi direferensikan. Memori tersebut disebut sampah. Ada banyak strategi untuk menerapkan pengumpulan sampah. Salah satunya adalah penghitungan referensi yang tujuannya adalah menghitung jumlah referensi ke objek dalam memori. Jika tidak ada lagi referensi ke suatu objek, objek tersebut dapat ditandai sebagai tidak digunakan lagi dan siap untuk pembersihan sampah memori. Pengumpul sampah PHP menggunakan penghitungan referensi, dan penggunaan fungsi xdebug_debug_zval() ekstensi Xdebug memungkinkan Anda melihat di baliknya. Pertimbangkan program PHP berikut.

<?php
  $a= (string) rand();
  $c = $b = $a;
  $b = 42;
  unset($c);
  $a = null;
?>

Program menetapkan angka acak yang di-casting ke string ke variabel baru bernama a. Kemudian, kode ini membuat dua variabel baru, b dan c, serta menetapkan nilai a ke variabel tersebut. Setelah itu, b akan ditetapkan ulang ke nomor 42, lalu c akan dibatalkan. Terakhir, tetapkan nilai a ke null. Dengan memberi anotasi pada setiap langkah program dengan xdebug_debug_zval(), Anda dapat melihat penghitung referensi pengumpul sampah sedang bekerja.

<?php
  $a= (string) rand();
  $c = $b = $a;
  xdebug_debug_zval('a');
  $b = 42;
  xdebug_debug_zval('a');
  unset($c);
  xdebug_debug_zval('a');
  $a = null;
  xdebug_debug_zval('a');
?>

Contoh di atas akan menghasilkan log berikut, tempat Anda melihat bagaimana jumlah referensi ke nilai variabel a berkurang setelah setiap langkah, yang masuk akal mengingat urutan kode. (Tentu saja, angka acak Anda akan berbeda.)

a:
(refcount=3, is_ref=0)string '419796578' (length=9)
a:
(refcount=2, is_ref=0)string '419796578' (length=9)
a:
(refcount=1, is_ref=0)string '419796578' (length=9)
a:
(refcount=0, is_ref=0)null

Ada tantangan lain dengan pengumpulan sampah, seperti mendeteksi siklus, tetapi untuk artikel ini, pemahaman tingkat dasar tentang penghitungan referensi sudah cukup.

Bahasa pemrograman diimplementasikan dalam bahasa pemrograman lain

Mungkin terasa seperti inception, tetapi bahasa pemrograman diimplementasikan dalam bahasa pemrograman lain. Misalnya, runtime PHP terutama diimplementasikan di C. Anda dapat melihat kode sumber PHP di GitHub. Kode pengumpulan sampah PHP terutama terletak di file zend_gc.c. Sebagian besar developer akan menginstal PHP melalui pengelola paket sistem operasi mereka. Namun, developer juga dapat membangun PHP dari kode sumber. Misalnya, di lingkungan Linux, langkah-langkah ./buildconf && ./configure && make akan membangun PHP untuk runtime Linux. Namun, ini juga berarti runtime PHP dapat dikompilasi untuk runtime lain, seperti, Anda sudah tahu, Wasm.

Metode tradisional untuk mentransfer bahasa ke runtime Wasm

Terlepas dari platform tempat PHP berjalan, skrip PHP dikompilasi menjadi bytecode yang sama dan dijalankan oleh Zend Engine. Zend Engine adalah compiler dan lingkungan runtime untuk bahasa skrip PHP. Zend Engine terdiri dari Zend Virtual Machine (VM), yang terdiri dari Zend Compiler dan Zend Executor. Bahasa seperti PHP yang diimplementasikan dalam bahasa tingkat tinggi lainnya seperti C biasanya memiliki pengoptimalan yang menargetkan arsitektur tertentu, seperti Intel atau ARM, dan memerlukan backend yang berbeda untuk setiap arsitektur. Dalam konteks ini, Wasm mewakili arsitektur baru. Jika VM memiliki kode khusus arsitektur, seperti kompilasi just-in-time (JIT) atau ahead-of-time (AOT), developer juga menerapkan backend untuk JIT/AOT untuk arsitektur baru. Pendekatan ini sangat masuk akal karena sering kali bagian utama codebase dapat dikompilasi ulang untuk setiap arsitektur baru.

Mengingat Wasm adalah bahasa tingkat rendah, wajar jika mencoba pendekatan yang sama di sana: Kompilasi ulang kode VM utama dengan parser, dukungan library, pengumpulan sampah, dan pengoptimalnya ke Wasm, lalu terapkan backend JIT atau AOT untuk Wasm jika diperlukan. Hal ini telah memungkinkan sejak MVP Wasm, dan berfungsi dengan baik dalam praktiknya di banyak kasus. Sebenarnya, PHP yang dikompilasi ke Wasm adalah yang mendukung WordPress Playground. Pelajari lebih lanjut project ini dalam artikel Membangun pengalaman WordPress dalam browser dengan WordPress Playground dan WebAssembly.

Namun, PHP Wasm berjalan di browser dalam konteks JavaScript bahasa host. Di Chrome, JavaScript dan Wasm dijalankan di V8, mesin JavaScript open source Google yang mengimplementasikan ECMAScript seperti yang ditentukan dalam ECMA-262. Selain itu, V8 sudah memiliki pengumpul sampah. Artinya, developer yang menggunakan, misalnya, PHP yang dikompilasi ke Wasm, akhirnya mengirimkan implementasi pengumpul sampah dari bahasa yang di-porting (PHP) ke browser yang sudah memiliki pengumpul sampah, yang sama sia-sianya dengan yang terdengar. Di sinilah WasmGC berperan.

Masalah lain dari pendekatan lama yang memungkinkan modul Wasm membuat GC-nya sendiri di atas memori linear Wasm adalah tidak adanya interaksi antara pengumpul sampah Wasm dan pengumpul sampah yang dibangun di atas bahasa yang dikompilasi ke Wasm, yang cenderung menyebabkan masalah seperti kebocoran memori dan upaya pengumpulan yang tidak efisien. Mengizinkan modul Wasm menggunakan kembali GC bawaan yang ada akan menghindari masalah ini.

Mengirim bahasa pemrograman ke runtime baru dengan WasmGC

WasmGC adalah proposal dari WebAssembly Community Group. Implementasi MVP Wasm saat ini hanya dapat menangani angka, yaitu bilangan bulat dan float, dalam memori linear, dan dengan proposal jenis referensi yang dikirimkan, Wasm juga dapat menyimpan referensi eksternal. WasmGC kini menambahkan jenis heap struct dan array, yang berarti dukungan untuk alokasi memori non-linear. Setiap objek WasmGC memiliki jenis dan struktur tetap, sehingga memudahkan VM membuat kode yang efisien untuk mengakses kolomnya tanpa risiko deoptimasi yang dimiliki bahasa dinamis seperti JavaScript. Dengan demikian, proposal ini menambahkan dukungan yang efisien untuk bahasa terkelola tingkat tinggi ke WebAssembly, melalui jenis heap struct dan array yang memungkinkan compiler bahasa yang menargetkan Wasm untuk berintegrasi dengan pengumpul sampah di VM host. Secara sederhana, ini berarti bahwa dengan WasmGC, mem-porting bahasa pemrograman ke Wasm berarti pengumpul sampah bahasa pemrograman tidak perlu lagi menjadi bagian dari port, tetapi pengumpul sampah yang ada dapat digunakan.

Untuk memverifikasi dampak peningkatan ini di dunia nyata, tim Wasm Chrome telah mengompilasi versi benchmark Fannkuch (yang mengalokasikan struktur data saat berfungsi) dari C, Rust, dan Java. Biner C dan Rust dapat berukuran antara 6,1 K hingga 9,6 K, bergantung pada berbagai flag compiler, sedangkan versi Java jauh lebih kecil, hanya 2,3 K. C dan Rust tidak menyertakan pengumpul sampah, tetapi tetap menggabungkan malloc/free untuk mengelola memori, dan alasan Java lebih kecil di sini adalah karena tidak perlu menggabungkan kode pengelolaan memori sama sekali. Ini hanyalah salah satu contoh spesifik, tetapi menunjukkan bahwa biner WasmGC berpotensi berukuran sangat kecil, dan ini bahkan sebelum ada upaya signifikan untuk mengoptimalkan ukuran.

Melihat cara kerja bahasa pemrograman yang di-porting WasmGC

Kotlin Wasm

Salah satu bahasa pemrograman pertama yang telah di-porting ke Wasm berkat WasmGC adalah Kotlin dalam bentuk Kotlin/Wasm. Demo, dengan kode sumber yang disediakan oleh tim Kotlin, ditampilkan dalam listingan berikut.

import kotlinx.browser.document
import kotlinx.dom.appendText
import org.w3c.dom.HTMLDivElement

fun main() {
    (document.getElementById("warning") as HTMLDivElement).style.display = "none"
    document.body?.appendText("Hello, ${greet()}!")
}

fun greet() = "world"

Sekarang Anda mungkin bertanya-tanya apa tujuannya, karena kode Kotlin di atas pada dasarnya terdiri dari JavaScript OM API yang dikonversi ke Kotlin. Hal ini mulai lebih masuk akal jika dikombinasikan dengan Compose Multiplatform, yang memungkinkan developer membangun UI yang mungkin sudah mereka buat untuk aplikasi Kotlin Android. Lihat eksplorasi awal ini dengan penampil gambar Kotlin/Wasm, yang juga disediakan oleh tim Kotlin.

Demo penampil gambar Kotlin/Wasm.

Dart dan Flutter

Tim Dart dan Flutter di Google juga sedang menyiapkan dukungan untuk WasmGC. Pekerjaan kompilasi Dart-ke-Wasm hampir selesai, dan tim sedang mengerjakan dukungan alat untuk mengirimkan aplikasi web Flutter yang dikompilasi ke WebAssembly. Anda dapat membaca tentang status pekerjaan saat ini di dokumentasi Flutter. Demo berikut adalah Pratinjau WasmGC Flutter.

Pelajari lebih lanjut WasmGC

Postingan blog ini baru membahas sedikit dan sebagian besar memberikan ringkasan umum WasmGC. Untuk mempelajari fitur ini lebih lanjut, buka link berikut:

Ucapan terima kasih

Artikel ini ditinjau oleh Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler, dan Rachel Andrew.