Có hai loại ngôn ngữ lập trình: ngôn ngữ lập trình thu gom rác và ngôn ngữ lập trình yêu cầu quản lý bộ nhớ theo cách thủ công. Ví dụ về ngôn ngữ lập trình hướng đối tượng có Kotlin, PHP hoặc Java. Ví dụ về ngôn ngữ thứ hai là C, C++ hoặc Rust. Theo nguyên tắc chung, các ngôn ngữ lập trình cấp cao hơn có nhiều khả năng có tính năng thu gom rác làm tính năng tiêu chuẩn. Trong bài đăng trên blog này, chúng tôi tập trung vào các ngôn ngữ lập trình thu gom rác và cách biên dịch các ngôn ngữ đó sang WebAssembly (Wasm). Nhưng trước tiên, thu gom rác (thường được gọi là GC) là gì?
Hỗ trợ trình duyệt
Thu gom rác
Nói một cách đơn giản, ý tưởng của việc thu gom rác là cố gắng lấy lại bộ nhớ do chương trình phân bổ nhưng không còn được tham chiếu nữa. Bộ nhớ như vậy được gọi là rác. Có nhiều chiến lược để triển khai tính năng thu gom rác. Một trong số đó là đếm tham chiếu, trong đó mục tiêu là đếm số lần tham chiếu đến các đối tượng trong bộ nhớ. Khi không còn tham chiếu nào đến một đối tượng, đối tượng đó có thể được đánh dấu là không còn được sử dụng và sẵn sàng để thu thập rác. Trình thu gom rác của PHP sử dụng tính năng đếm tham chiếu và việc sử dụng hàm xdebug_debug_zval()
của tiện ích Xdebug cho phép bạn xem xét kỹ hơn. Hãy xem xét chương trình PHP sau.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Chương trình gán một số ngẫu nhiên được truyền vào một chuỗi cho một biến mới có tên là a
. Sau đó, hàm này sẽ tạo hai biến mới là b
và c
, đồng thời chỉ định giá trị a
cho các biến đó. Sau đó, hàm này gán lại b
cho số 42
, rồi huỷ thiết lập c
. Cuối cùng, hàm này đặt giá trị của a
thành null
. Chú thích từng bước của chương trình bằng xdebug_debug_zval()
, bạn có thể thấy bộ đếm tham chiếu của trình thu gom rác đang hoạt động.
<?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');
?>
Ví dụ trên sẽ xuất ra các nhật ký sau đây, trong đó bạn thấy số lượng tham chiếu đến giá trị của biến a
giảm dần sau mỗi bước, điều này hợp lý với trình tự mã. (Tất nhiên, số ngẫu nhiên của bạn sẽ khác.)
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
Có những thách thức khác với việc thu gom rác, chẳng hạn như phát hiện chu kỳ, nhưng đối với bài viết này, bạn chỉ cần hiểu cơ bản về tính năng đếm tham chiếu là đủ.
Ngôn ngữ lập trình được triển khai trong các ngôn ngữ lập trình khác
Có thể bạn sẽ thấy khó hiểu, nhưng các ngôn ngữ lập trình được triển khai trong các ngôn ngữ lập trình khác. Ví dụ: môi trường thời gian chạy PHP chủ yếu được triển khai trong C. Bạn có thể xem mã nguồn PHP trên GitHub. Mã thu gom rác của PHP chủ yếu nằm trong tệp zend_gc.c
. Hầu hết các nhà phát triển sẽ cài đặt PHP thông qua trình quản lý gói của hệ điều hành. Tuy nhiên, nhà phát triển cũng có thể tạo PHP từ mã nguồn. Ví dụ: trong môi trường Linux, các bước ./buildconf && ./configure && make
sẽ tạo PHP cho môi trường thời gian chạy Linux. Nhưng điều này cũng có nghĩa là môi trường thời gian chạy PHP có thể được biên dịch cho các môi trường thời gian chạy khác, chẳng hạn như Wasm.
Các phương thức truyền thống để chuyển ngôn ngữ sang môi trường thời gian chạy Wasm
Một cách độc lập với nền tảng PHP đang chạy, các tập lệnh PHP được biên dịch thành cùng mã byte và được Zend Engine chạy. Zend Engine là một trình biên dịch và môi trường thời gian chạy cho ngôn ngữ tập lệnh PHP. Bộ công cụ này bao gồm Máy ảo (VM) Zend, gồm có Zend Compiler và Zend Executor. Các ngôn ngữ như PHP được triển khai trong các ngôn ngữ cấp cao khác như C thường có các tính năng tối ưu hoá nhắm đến các cấu trúc cụ thể, chẳng hạn như Intel hoặc ARM, đồng thời yêu cầu một phần phụ trợ khác nhau cho mỗi cấu trúc. Trong ngữ cảnh này, Wasm đại diện cho một cấu trúc mới. Nếu máy ảo có mã dành riêng cho kiến trúc, chẳng hạn như biên dịch đúng thời điểm (JIT) hoặc biên dịch trước thời gian (AOT), thì nhà phát triển cũng triển khai phần phụ trợ cho JIT/AOT cho kiến trúc mới. Phương pháp này rất hợp lý vì thường thì bạn chỉ có thể biên dịch lại phần chính của cơ sở mã cho mỗi cấu trúc mới.
Do Wasm ở cấp thấp, nên bạn có thể thử áp dụng phương pháp tương tự: Biên dịch lại mã máy ảo chính bằng trình phân tích cú pháp, hỗ trợ thư viện, thu gom rác và trình tối ưu hoá cho Wasm, đồng thời triển khai phần phụ trợ JIT hoặc AOT cho Wasm nếu cần. Điều này đã có thể thực hiện được kể từ MVP Wasm và hoạt động hiệu quả trong thực tế trong nhiều trường hợp. Trong thực tế, PHP được biên dịch thành Wasm chính là công cụ hỗ trợ WordPress Playground. Tìm hiểu thêm về dự án này trong bài viết Xây dựng trải nghiệm WordPress trong trình duyệt bằng WordPress Playground và WebAssembly.
Tuy nhiên, PHP Wasm chạy trong trình duyệt trong ngữ cảnh của ngôn ngữ lưu trữ JavaScript. Trong Chrome, JavaScript và Wasm chạy trong V8, công cụ JavaScript nguồn mở của Google triển khai ECMAScript như được chỉ định trong ECMA-262. Đồng thời, V8 đã có trình thu gom rác. Điều này có nghĩa là các nhà phát triển sử dụng, ví dụ: PHP được biên dịch sang Wasm, cuối cùng sẽ gửi một bản triển khai trình thu gom rác của ngôn ngữ được chuyển (PHP) đến trình duyệt đã có trình thu gom rác, điều này thật lãng phí. Đây là lúc WasmGC phát huy tác dụng.
Vấn đề khác của phương pháp cũ là cho phép các mô-đun Wasm tạo GC riêng dựa trên bộ nhớ tuyến tính của Wasm là không có sự tương tác giữa trình thu gom rác của riêng Wasm và trình thu thập rác tích hợp trên ngôn ngữ được biên dịch thành Wasm, điều này có xu hướng gây ra các sự cố như rò rỉ bộ nhớ và nỗ lực thu thập không hiệu quả. Việc cho phép các mô-đun Wasm sử dụng lại GC tích hợp hiện có sẽ giúp tránh được những vấn đề này.
Chuyển đổi ngôn ngữ lập trình sang môi trường thời gian chạy mới bằng WasmGC
WasmGC là một đề xuất của Nhóm cộng đồng WebAssembly. Việc triển khai Wasm MVP hiện tại chỉ có thể xử lý các số (ví dụ: số nguyên và số thực có độ chính xác đơn) trong bộ nhớ tuyến tính. Ngoài ra, Wasm có thể giữ lại các tham chiếu bên ngoài với các loại tham chiếu được đề xuất. WasmGC hiện thêm các loại vùng nhớ khối xếp cấu trúc và mảng, tức là hỗ trợ phân bổ bộ nhớ phi tuyến tính. Mỗi đối tượng WasmGC có một loại và cấu trúc cố định, giúp các máy ảo dễ dàng tạo mã hiệu quả để truy cập vào các trường của chúng mà không có nguy cơ giảm tối ưu hoá mà các ngôn ngữ động như JavaScript có. Do đó, đề xuất này sẽ bổ sung tính năng hỗ trợ hiệu quả cho các ngôn ngữ được quản lý cấp cao vào WebAssembly, thông qua các loại vùng nhớ khối xếp cấu trúc và mảng cho phép trình biên dịch ngôn ngữ nhắm đến Wasm tích hợp với trình thu gom rác trong máy ảo lưu trữ. Nói một cách đơn giản, điều này có nghĩa là với WasmGC, việc chuyển đổi một ngôn ngữ lập trình sang Wasm có nghĩa là trình thu gom rác của ngôn ngữ lập trình không còn cần phải là một phần của cổng nữa, mà thay vào đó, bạn có thể sử dụng trình thu gom rác hiện có.
Để xác minh tác động thực tế của điểm cải tiến này, nhóm Wasm của Chrome đã tổng hợp các phiên bản điểm chuẩn Fannkuch (phân bổ cấu trúc dữ liệu khi hoạt động) từ C, Rust và Java. Tệp nhị phân C và Rust có thể có kích thước từ 6,1 K đến 9,6 K tuỳ thuộc vào các cờ trình biên dịch, trong khi phiên bản Java nhỏ hơn nhiều chỉ ở mức 2,3 K! C và Rust không bao gồm trình thu gom rác, nhưng vẫn đóng gói malloc/free
để quản lý bộ nhớ. Lý do Java nhỏ hơn ở đây là vì Java không cần đóng gói bất kỳ mã quản lý bộ nhớ nào. Đây chỉ là một ví dụ cụ thể, nhưng nó cho thấy rằng các tệp nhị phân WasmGC có khả năng rất nhỏ, ngay cả trước khi có bất kỳ công việc đáng kể nào về việc tối ưu hoá kích thước.
Xem ngôn ngữ lập trình được chuyển đổi WasmGC đang hoạt động
Wasm trong Kotlin
Một trong những ngôn ngữ lập trình đầu tiên được chuyển sang Wasm nhờ WasmGC là Kotlin ở dạng Kotlin/Wasm. Bản minh hoạ, với mã nguồn do nhóm Kotlin cung cấp, được hiển thị trong danh sách sau.
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"
Bây giờ, bạn có thể đang thắc mắc vấn đề đó là gì, vì về cơ bản, mã Kotlin ở trên bao gồm các API OM của JavaScript được chuyển đổi thành Kotlin. Điều này trở nên hợp lý hơn khi kết hợp với Compose Multiplatform, cho phép nhà phát triển xây dựng dựa trên giao diện người dùng mà họ có thể đã tạo cho các ứng dụng Kotlin trên Android. Hãy xem phần khám phá ban đầu về điều này bằng bản minh hoạ trình xem hình ảnh Kotlin/Wasm và khám phá mã nguồn của trình xem này, cũng do nhóm Kotlin cung cấp.
Dart và Flutter
Nhóm Dart và Flutter tại Google cũng đang chuẩn bị hỗ trợ cho WasmGC. Công việc biên dịch Dart-to- Wasm sắp hoàn tất và nhóm đang nghiên cứu hỗ trợ công cụ nhằm phân phối các ứng dụng web Flutter được biên dịch cho WebAssembly. Bạn có thể đọc về trạng thái hiện tại của công việc trong tài liệu về Flutter. Bản minh hoạ sau đây là Bản xem trước của Flutter WasmGC.
Tìm hiểu thêm về WasmGC
Bài đăng trên blog này hầu như không gây ra những vấn đề gì và hầu như cung cấp cái nhìn tổng quan ở cấp cao về WasmGC. Để tìm hiểu thêm về tính năng này, hãy truy cập vào các đường liên kết sau:
- Một cách mới để đưa các ngôn ngữ lập trình thu gom rác vào WebAssembly một cách hiệu quả
- Tổng quan về WasmGC
- MVP WasmGC
- WasmGC sau MVP
Lời cảm ơn
Bài viết này đã được Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler và Rachel Andrew xem xét.