Zig nâng cấp ArrayList với Pointer Stability và hàng loạt cải tiến mới cho trình biên dịch
Bài viết tổng hợp các thay đổi mới nhất trên nhánh chính của ngôn ngữ lập trình Zig, tập trung vào tính năng khóa ổn định con trỏ (Pointer Stability Locks) cho std.ArrayList, giúp phát hiện lỗi bộ nhớ sớm. Ngoài ra, bài viết cũng đề cập đến cải tiến lớn cho trình biên dịch, từ tối ưu hóa LLVM backend, triển khai ngữ nghĩa mới cho @bitCast, đến việc tách quy trình build và cải thiện tốc độ biên dịch tăng dần.
Zig giới thiệu Pointer Stability cho ArrayList và loạt cải tiến đột phá cho trình biên dịch
Bản cập nhật mới nhất của ngôn ngữ lập trình Zig trên nhánh chính mang đến nhiều thay đổi quan trọng, nổi bật nhất là tính năng khóa ổn định con trỏ (Pointer Stability Locks) cho std.ArrayList, giúp lập trình viên phát hiện lỗi tham chiếu bộ nhớ nguy hiểm ngay tại thời điểm chạy thay vì phải debug khó khăn. Bên cạnh đó, các cải tiến cho trình biên dịch bao gồm tối ưu hóa LLVM backend, ngữ nghĩa mới cho builtin @bitCast và kiến trúc build system tách rời hứa hẹn mang lại hiệu suất biên dịch vượt trội cho phiên bản 0.17.0 sắp tới.
Pointer Stability Locks đến với std.ArrayList
Sau khi được thêm vào các container Hash Map của thư viện chuẩn từ năm 2024, kỹ thuật đảm bảo an toàn bộ nhớ này hiện đã được áp dụng cho std.ArrayList nhờ một pull request từ Leo Emar-Kar.
Để sử dụng, lập trình viên chỉ cần gọi lockPointers() khi lưu trữ một con trỏ hoặc slice trỏ đến phần tử của ArrayList, và gọi unlockPointers() khi không còn sử dụng các con trỏ đó nữa.
Ví dụ minh họa trong bài viết cho thấy một lỗi tiềm ẩn khi lưu trữ các chunk dữ liệu tham chiếu đến nội dung của một ArrayList khác. Khi ArrayList gốc tăng kích thước, vùng nhớ có thể bị di chuyển, khiến các con trỏ cũ trở nên lỗi thời.
Nếu không có cơ chế bảo vệ, chương trình có thể chạy với dữ liệu sai mà không có bất kỳ cảnh báo nào. Nhưng với lockPointers(), Zig sẽ ngay lập tức đưa ra một panic kèm stack trace chỉ rõ vị trí vi phạm, giúp lập trình viên nhanh chóng xác định nguyên nhân lỗi bộ nhớ.
"Thật tuyệt, điều này giúp ích rất nhiều: giờ tôi có thể thấy rằng tôi nên xem xét các vấn đề an toàn bộ nhớ là nguyên nhân có thể gây ra lỗi kiểm thử của mình, thay vì chỉ tập trung vào vấn đề logic."
Một điểm tinh tế cần lưu ý: không giống như HashMap, ArrayList có thứ tự, nghĩa là các thao tác như orderedRemove() hoặc pop() có thể di chuyển các phần tử mà không cần cấp phát lại bộ nhớ. Vì vậy, ngay cả những hàm không cấp phát này cũng sẽ kích hoạt assertion sau khi gọi lockPointers().
Cải tiến lớn cho trình biên dịch: LLVM backend và @bitCast mới
Một thay đổi quan trọng khác là việc tối ưu hóa cách LLVM backend xử lý các kiểu số nguyên có độ rộng bit tùy ý (ví dụ u4, i13). Trước đây, Zig hạ thấp trực tiếp các kiểu này xuống bit-int của LLVM, nhưng điều này không tối ưu và gây ra nhiều lỗi. Giải pháp mới là chỉ sử dụng bit-int trong SSA form và mở rộng chúng sang kích thước ABI chuẩn khi lưu trữ trong bộ nhớ.
Quá trình này dẫn đến việc triển khai ngữ nghĩa mới cho builtin @bitCast. Thay vì dựa trên việc diễn giải lại các byte trong bộ nhớ (phụ thuộc vào endian), @bitCast giờ được định nghĩa dựa trên "bố cục bit logic" của kiểu dữ liệu — một cách tiếp cận không phụ thuộc vào endian và nhất quán trên mọi nền tảng.
Ví dụ, chuyển đổi [2]u8 thành u16 giờ hoạt động giống nhau trên mọi kiến trúc: phần tử đầu tiên luôn trở thành 8 bit thấp nhất. Điều này cho phép nhiều phép toán mới thú vị, chẳng hạn như chuyển một số nguyên thành vector các bit riêng lẻ.
Kết quả đáng kinh ngạc là bản thân trình biên dịch Zig đã cải thiện hiệu suất khoảng 5% nhờ tối ưu hóa tốt hơn từ LLVM, hứa hẹn mang lại lợi ích cho người dùng trong phiên bản 0.17.0.
Kiến trúc build system mới: Tách quá trình maker và configurer
Một thay đổi kiến trúc lớn đã được triển khai nhằm tăng tốc zig build theo ba cách:
- Chỉ logic
build.zigcủa người dùng được biên dịch lại mỗi khi thay đổi, thay vì toàn bộ hệ thống build - Bỏ qua việc chạy lại logic
build.zigkhi không có gì thay đổi, nhờ sử dụng cấu hình đã được tuần tự hóa từ lần chạy trước - Quá trình thực thi build graph được biên dịch với chế độ tối ưu hóa
Quá trình configurer (biên dịch build.zig, tạo cấu hình nhị phân) tách biệt hoàn toàn khỏi quá trình maker (thực thi build graph). Điều này cũng cho phép chuyển các lệnh quản lý package sang quá trình maker, giúp chúng có thể được vá mà không cần biên dịch lại trình biên dịch, đồng thời kích hoạt các kiểm tra an toàn và sử dụng các chỉ dẫn CPU đặc biệt khi xử lý mạng.
ELF linker mới và biên dịch tăng dần
Linker ELF mới ra mắt trong Zig 0.16.0 đã đạt cột mốc quan trọng khi có thể tự xây dựng trình biên dịch Zig với LLVM và LLD. Điểm nổi bật nhất là hỗ trợ biên dịch tăng dần cực nhanh, cho phép xây dựng lại dự án trong vài mili giây. Hiện tại, tính năng này hoạt động tốt trên x86_64 Linux, mặc dù chưa hỗ trợ tạo thông tin debug DWARF cho mã Zig — đây là ưu tiên tiếp theo của nhóm phát triển.
Các cải tiến khác đáng chú ý
- Cải tiến độ phân giải kiểu dữ liệu: Trình biên dịch giờ lười phân tích các trường của kiểu hơn, có thể bỏ qua các lỗi compile trong các trường không bao giờ được khởi tạo — giúp giảm tải mã không cần thiết.
- Thông báo lỗi dependency loop rõ ràng hơn: Người dùng sẽ nhận được thông điệp chi tiết chỉ ra chính xác nguồn gốc của vòng lặp phụ thuộc.
- Tối ưu hóa Windows API: Zig chuyển sang sử dụng Native API (ntdll.dll) thay vì các wrapper kernel32, giúp tránh các lần cấp phát heap không cần thiết và giảm độ trễ không xác định, đặc biệt là khi đọc dữ liệu ngẫu nhiên.
- Thay thế libc: Khoảng 250 tệp C đã bị xóa khỏi kho lưu trữ Zig khi các hàm libc được thay thế bằng wrapper của thư viện chuẩn, giúp giảm kích thước cài đặt, cải thiện tốc độ biên dịch và cho phép tối ưu hóa liên kết toàn cục.
Lời kết
Với phiên bản 0.17.0 dự kiến phát hành trong vài tuần tới, Zig đang cho thấy sự trưởng thành vượt bậc cả về ngôn ngữ lẫn hệ sinh thái công cụ. Từ an toàn bộ nhớ cho cấu trúc dữ liệu phổ biến như ArrayList, đến hiệu suất biên dịch tăng đáng kể, cộng đồng Zig chắc chắn có nhiều điều đáng mong đợi.