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 lainnya, adalah Kotlin, PHP, atau Java. Contoh yang terakhir adalah C, C++, atau Rust. Sebagai aturan umum, bahasa pemrograman tingkat tinggi cenderung memiliki pembersihan sampah memori sebagai fitur standar. Dalam postingan blog ini, fokusnya adalah pada bahasa pemrograman yang mengumpulkan sampah tersebut dan bagaimana bahasa tersebut dapat dikompilasi ke WebAssembly (Wasm). Namun, apa itu pengumpulan sampah (sering disebut GC)?

Dukungan Browser

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: tidak didukung.

Pembersihan sampah memori

Singkatnya, pembersihan sampah memori adalah upaya untuk mendapatkan kembali memori yang dialokasikan oleh program, tetapi tidak lagi direferensikan. Memori seperti itu disebut sampah. Ada banyak strategi untuk mengimplementasikan pembersihan sampah memori. Salah satunya adalah penghitungan referensi yang bertujuan untuk 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 menetapkan angka acak yang ditransmisikan ke string ke variabel baru yang disebut a. Kemudian, metode 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. (Angka acak Anda tentunya 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 memori 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 lingkungan compiler dan 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 memanfaatkan, misalnya, PHP yang dikompilasi ke Wasm, pada akhirnya mengirimkan implementasi bahasa yang di-porting (PHP) ke browser yang sudah memiliki pembersih sampah, yang kedengarannya boros. 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. Membiarkan modul Wasm menggunakan kembali GC bawaan yang ada akan menghindari masalah ini.

Mentransfer 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 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 guna mengakses kolomnya tanpa risiko de-pengoptimalan 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 nyata dari peningkatan ini, tim Wasm Chrome telah menyusun versi tolok ukur 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 salah satu contoh spesifik, tetapi menunjukkan bahwa biner WasmGC memiliki potensi yang sangat kecil, dan ini bahkan sebelum dilakukan pekerjaan signifikan pada pengoptimalan ukuran.

Melihat cara kerja bahasa pemrograman yang di-porting WasmGC

Wasm Kotlin

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 gunanya, 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 tentang hal ini dengan demo penampil gambar Kotlin/Wasm dan jelajahi kode sumber, juga dari 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 WasmGC Flutter.

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.