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

Ada dua jenis bahasa pemrograman: bahasa pemrograman yang melakukan pembersihan 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 tinggi lebih cenderung memiliki pembersihan sampah sebagai fitur standar. Dalam postingan blog ini, fokusnya adalah pada bahasa pemrograman yang dikumpulkan sampah dan cara mengompilasikannya ke WebAssembly (Wasm). Namun, apa itu pengumpulan sampah (sering disebut GC)?

Dukungan Browser

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

Pembersihan sampah memori

Dalam istilah yang disederhanakan, ide pengumpulan sampah adalah upaya untuk mengambil 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 objek, objek tersebut dapat ditandai sebagai tidak digunakan lagi sehingga siap untuk pembersihan sampah memori. Pengumpul sampah PHP menggunakan penghitungan referensi, dan menggunakan fungsi xdebug_debug_zval() ekstensi Xdebug memungkinkan Anda melihat di balik layarnya. Pertimbangkan program PHP berikut.

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

Program ini menetapkan angka acak yang ditransmisikan ke string ke variabel baru yang disebut a. Kemudian, kode ini akan membuat dua variabel baru, b dan c, dan menetapkan nilai a untuk keduanya. Setelah itu, b akan ditetapkan ulang ke nomor 42, lalu c akan dibatalkan penetapannya. Terakhir, kode ini menetapkan nilai a ke null. Dengan menganotasi setiap langkah program dengan xdebug_debug_zval(), Anda dapat melihat penghitung referensi pengumpulan sampah yang 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 terkait pembersihan sampah, seperti mendeteksi siklus, tetapi untuk artikel ini, cukup memiliki pemahaman tingkat dasar tentang penghitungan referensi.

Bahasa pemrograman diterapkan dalam bahasa pemrograman lain

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

Metode tradisional untuk melakukan transfer 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. Ini terdiri dari Virtual Machine (VM) Zend, 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 tepat waktu (JIT) atau kompilasi mendahului waktu (AOT), developer juga akan menerapkan backend untuk JIT/AOT untuk arsitektur baru. Pendekatan ini sangat masuk akal karena sering kali bagian utama codebase hanya dapat dikompilasi ulang untuk setiap arsitektur baru.

Mengingat tingkat rendah Wasm, wajar jika Anda mencoba pendekatan yang sama di sana: Kompilasi ulang kode VM utama dengan parser, dukungan library, pembersihan sampah, dan pengoptimalnya ke Wasm, serta terapkan backend JIT atau AOT untuk Wasm jika diperlukan. Hal ini telah dimungkinkan sejak MVP Wasm, dan dalam banyak kasus, hal ini berfungsi dengan baik dalam praktik. Bahkan, PHP yang dikompilasi ke Wasm adalah yang mendukung WordPress Playground. Pelajari project ini lebih lanjut dalam artikel Mem-build 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 pengumpulan sampah. Artinya, developer yang menggunakan, misalnya, PHP yang dikompilasi ke Wasm, akhirnya mengirimkan implementasi garbage collector dari bahasa yang di-port (PHP) ke browser yang sudah memiliki garbage collector, yang sama sia-sianya dengan kedengarannya. Di sinilah WasmGC berperan.

Masalah lain dari pendekatan lama yang memungkinkan modul Wasm mem-build GC-nya sendiri di atas memori linear Wasm adalah tidak adanya interaksi antara pengumpulan sampah Wasm sendiri dan pengumpulan sampah yang dibuat 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.

Mentransfer bahasa pemrograman ke runtime baru dengan WasmGC

WasmGC adalah proposal dari Grup Komunitas WebAssembly. Implementasi MVP Wasm saat ini hanya dapat menangani angka, yaitu bilangan bulat dan float, dalam memori linear, dan dengan proposal jenis referensi yang dikirim, 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, yang memudahkan VM untuk menghasilkan kode yang efisien untuk mengakses kolomnya tanpa risiko deoptimizations 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 pengumpulan sampah di VM host. Dalam istilah yang disederhanakan, ini berarti bahwa dengan WasmGC, mem-port bahasa pemrograman ke Wasm berarti bahwa pengumpulan sampah memori bahasa pemrograman tidak perlu lagi menjadi bagian dari port, tetapi pengumpulan sampah memori 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 berkisar dari 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 pengumpulan sampah, tetapi keduanya masih memaketkan malloc/free untuk mengelola memori, dan alasan Java lebih kecil di sini adalah karena Java tidak perlu memaketkan kode pengelolaan memori sama sekali. Ini hanyalah satu contoh spesifik, tetapi menunjukkan bahwa biner WasmGC berpotensi 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 dari 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 Multiplatform Compose, yang memungkinkan developer mem-build berdasarkan UI yang mungkin sudah mereka buat untuk aplikasi Android Kotlin. Lihat eksplorasi awal ini dengan demo pelihat gambar Kotlin/Wasm dan jelajahi kode sumbernya, yang juga disediakan oleh tim Kotlin.

Dart dan Flutter

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

Pelajari WasmGC lebih lanjut

Postingan blog ini baru membahas sedikit dan sebagian besar memberikan ringkasan umum tentang WasmGC. Untuk mempelajari fitur ini lebih lanjut, lihat 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.