- Nhánh master của Zig đã hợp nhất cải tiến xử lý số nguyên không theo kích thước ABI trong backend LLVM cùng ngữ nghĩa
@bitCastmới, xử lý đồng thời các vấn đề tối ưu hóa và sự không nhất quán trong hành vi ngôn ngữ - Các số nguyên có độ rộng bit tùy ý như
u4,i13,u40giờ vẫn được xử lý như bit-int ở giá trị SSA, nhưng khi lưu vào bộ nhớ sẽ được mở rộng thành số nguyên có kích thước ABI @bitCasttrước đây gần với việc diễn giải lại byte trong bộ nhớ, nhưng định nghĩa mới diễn giải dựa trên mảng bit logic của kiểu, giúp giảm phụ thuộc vào endian- Thay đổi này đã được mở rộng tới backend LLVM·C và cả thực thi
comptime, đồng thời các chỗ sử dụng liên quan trong thư viện chuẩn, trình biên dịch vàcompiler_rtcũng được rà soát - Khi các tối ưu hóa LLVM từng bị bỏ lỡ được khôi phục, trình biên dịch Zig ghi nhận cải thiện hiệu năng khoảng 5%, và có thể kỳ vọng một số cải thiện hiệu năng runtime trong 0.17.0
Thay đổi trong xử lý số nguyên có độ rộng bit tùy ý của backend LLVM
- Trước đây, Zig hạ trực tiếp các kiểu số nguyên có độ rộng bit tùy ý như
u4,i13,u40xuống các kiểu bit-int của LLVM IR nhưi4,i13,i40 - Cách làm này khiến ngữ nghĩa biểu diễn bộ nhớ của LLVM tạo ra những ràng buộc không cần thiết cho bộ tối ưu hóa, và vì Clang không tạo LLVM IR như vậy nên các nhánh xử lý bên trong LLVM cũng không được kiểm thử đầy đủ
- Trong vài năm qua, đã thực sự quan sát thấy các trường hợp bỏ lỡ tối ưu hóa và biên dịch sai
- Cách làm mới vẫn giữ kiểu bit-int cho thao tác trên giá trị SSA, nhưng khi lưu vào bộ nhớ sẽ zero-extend hoặc sign-extend sang các kiểu có kích thước ABI như
i8,i16,i32 - Kiểu lowering này khớp với cách Clang lowering
_BitInt(N)của C, nên được kỳ vọng sẽ đi theo nhánh được LLVM hỗ trợ tốt hơn
Giới hạn của @bitCast trước đây
- Về mặt khái niệm,
@bitCasttrước đây gần với hành vi sau- Lấy con trỏ tới giá trị toán hạng
- Ép kiểu con trỏ đó sang con trỏ của kiểu đích
- Tải giá trị từ con trỏ đó
- Tức là định nghĩa cũ gần với việc diễn giải lại byte trong bộ nhớ hơn là cấu trúc logic của kiểu
- Theo thời gian, hành vi thực tế đã lệch khỏi định nghĩa này, và dù trên hầu hết target
@sizeOf(u24)lớn hơn@sizeOf([3]u8), việc@bitCasttừ[3]u8sangu24vẫn được cho phép - Backend LLVM đã hiện thực ngữ nghĩa
@bitCastchưa được đặc tả đầy đủ, và khi thay đổi cách lưu số nguyên vào bộ nhớ, bộ kiểm thử của trình biên dịch đã xuất hiện Illegal Behavior và crash - Thay vì thêm logic vào backend LLVM để bắt chước hành vi cũ, hướng được chọn là triển khai toàn diện định nghĩa
@bitCastmới
Ngữ nghĩa @bitCast mới
- Ngữ nghĩa mới dựa trên đề xuất ngôn ngữ #19755 được gửi và chấp nhận trong năm 2024
- Ngữ nghĩa này đã được hiện thực trong backend self-hosted x86_64, và với thay đổi lần này đã được mở rộng tới backend LLVM·C và cả thực thi
comptime @bitCastmới hoạt động dựa trên thứ tự bit biểu diễn logic của kiểu, chứ không phải byte trong bộ nhớu5gồm 5 bit logic từ least-significant bit đến most-significant bit[2]u5gồm 10 bit logic, trong đó 5 bit của phần tử đầu tiên nối tiếp 5 bit của phần tử thứ hai
- Với các chuyển đổi đơn giản giữa số nguyên, như đổi
u8sangi8cùng kích thước, các bit được giữ nguyên và bit cao nhất được diễn giải như bit dấu - Ngữ nghĩa
@bitCastgiữa kiểu số nguyên vàpacked structhoặcpacked unioncũng được giữ nguyên
Hành vi thay đổi với mảng·vector
- Điểm mà ngữ nghĩa mới khác với trước đây là khi có các kiểu aggregate như mảng và vector tham gia
- Ví dụ, nếu
@bitCasttừ[2]u8sangu16, thì theo ngữ nghĩa cũ kết quả sẽ khác nhau tùy endian của target- Trên target big-endian, phần tử đầu tiên của mảng trở thành 8 bit cao
- Trên target little-endian, phần tử đầu tiên của mảng trở thành 8 bit thấp
- Ngữ nghĩa mới chỉ xét biểu diễn bit logic nên độc lập với endian, và trên mọi target phần tử đầu tiên của mảng sẽ trở thành 8 bit thấp
- Nói chung, điều này gần với hành vi cũ trên target little-endian hơn
- Cũng có thể thực hiện các chuyển đổi không điển hình như từ
[2]u3sang@Vector(3, u2)- Nối các bit logic của mảng lại rồi đọc theo đơn vị 2 bit để tạo thành các phần tử vector
- Điều này cũng có thể dùng để
@bitCastmột số nguyên sang@Vector(n, u1)nhằm tách thành vector các bit riêng lẻ
Các đề xuất được áp dụng cùng lúc và quá trình di trú
- Trong quá trình này, một số đề xuất nhỏ liên quan tới
@bitCastcũng được hiện thực cùng lúc - Vì ngữ nghĩa mới khác biệt đáng kể so với cũ, các chỗ dùng
@bitCasttrong thư viện chuẩn, trình biên dịch và các thư viện hỗ trợ nhưcompiler_rtđã được rà soát - PR liên quan là codeberg.org/ziglang/zig/pulls/35711, và khi được hợp nhất vào master, nhiều issue cũng đã được đóng cùng lúc
- Ngữ nghĩa đã thay đổi và quy trình di trú được khuyến nghị sẽ được tổng hợp trong ghi chú phát hành Zig 0.17.0
Hiệu quả hiệu năng có thể kỳ vọng trong 0.17.0
- Mục tiêu ban đầu là thay đổi lowering số nguyên không theo kích thước ABI trong backend LLVM, và việc này đã thành công trong việc khôi phục các tối ưu hóa từng bị bỏ lỡ
- Kết quả liên quan có thể xem tại demonstrably successful
- Bản thân trình biên dịch Zig, dù nội bộ không dùng quá nhiều số nguyên có độ rộng bit tùy ý, vẫn cho thấy cải thiện hiệu năng khoảng 5% nhờ tối ưu hóa tốt hơn
- Trong 0.17.0, một số đoạn mã có thể nhận được cải thiện nhỏ về hiệu năng runtime
1 bình luận
Các ý kiến trên Lobste.rs
Bài viết nói rằng biểu diễn bit logic là độc lập với endian, nhưng phần giải thích thực tế trông rõ ràng là theo kiểu little-endian, không hỗ trợ thứ tự bit hay thứ tự byte big-endian
Đây là nhật ký phát triển mới ngày 25/6/2026, nói rằng ngữ nghĩa
@bitCastmới và các cải tiến backend LLVM đã được hợp nhất trong pull request gần đâyKhá thú vị, nhưng tôi tự hỏi liệu trên các đích big-endian vốn hiếm khi được kiểm thử, những đoạn mã viết như dưới đây có thể đột ngột bị hỏng không
Viết bằng mã giả không phải Zig:
Thực ra có lẽ không phải vấn đề lớn; trong số hàng nghìn
@bitCasttrong kho Zig, hình như số chỗ bị ảnh hưởng bởi thay đổi này ít hơn 100 rất nhiềuThành thật mà nói, tôi cũng không nghĩ phần lớn người dùng Zig thật sự biết chính xác
@bitCasthoạt động thế nào khi chuyển đổi giữa mảng/vector và scalar. Trước đây nhiều đoạn mã chỉ được kiểm thử trên hệ thống của tác giả và chỉ chạy đúng trên little-endian, còn giờ có lẽ sẽ chạy được ở mọi nơiVới tư cách một lập trình viên C đời cũ, tôi nhớ rằng bit field của C không mấy được ưa chuộng vì hành vi của chúng không khả chuyển giữa các kiến trúc
Ngữ nghĩa
@bitCastmới của Zig là ngữ nghĩa trừu tượng khả chuyển, cho cùng kết quả trên các kiến trúc khác nhau, nên tôi nghĩ đây đúng là hướng cần thiếtGần đây tôi đang thiết kế bit field và bit cast cho ngôn ngữ của mình, nên tôi định xem kỹ hơn tài liệu thiết kế và triển khai của Zig để làm rõ mã của tôi nên hoạt động như thế nào
packed structvàpacked union, và cả hai đều được định nghĩa để khớp tốt với định nghĩa@bitCastmớipacked structhoạt động bằng cách nhét các bit của trường vào “số nguyên nền”. Ví dụ nếu các trường làbool,u6,i9và số nguyên nền làu16, thì bit thấp nhất củau16làbool, 6 bit tiếp theo làu6, và 9 bit còn lại lài9. Nói cách khác, packed struct của Zig gần như là cú pháp tiện dụng đặt trên nhiều phép shift và maskpacked unioncũng có số nguyên nền, nhưng mọi trường phải dùng đúng cùng số bit với số nguyên nền. Vì vậy việc ghi vào một trường rồi đọc từ trường khác gần như tương đương với@bitCasttheo ngữ nghĩa mới. Tuy nhiên các trường củapacked union/packed structkhông thể có kiểu mảng hoặc vectorCá nhân tôi thấy các công cụ này rất phù hợp để biểu diễn “cấu trúc liên quan đến bit”. Có thể đóng gói nhiều giá trị bằng
packed structđể dùng như bit field trong C, và vì đây là cú pháp tiện dụng trên các phép toán bit, nên cả bit flag vốn trong C thường phải xử lý bằng một đống macro không an toàn kiểu cũng có thể được biểu diễn gọn gàngVí dụ, với các cờ truy cập RWX, trong C có thể nhận qua các macro
ACCESS_READ,ACCESS_WRITE,ACCESS_EXECvà APIuint8_t, còn trong Zig có thể định nghĩaAccess = packed struct(u8)với các trườngread,write,exec,reservedvà để API nhậnAccessDùng
packed structvàpacked unioncũng có thể biểu diễn những bố trí bit khá kỳ lạ. Trong entry bảng ký hiệu của định dạng đối tượng Mach-O có trườngn_typeđặc biệt, có vẻ do lý do lịch sử; có thể mô hình hóa nó dưới dạngbits: packed struct(u8)vàstab: enum(u8)bên trongpacked union(u8)Khi xử lý giá trị
n_typenày, không cần shift hay mask thủ công. Chỉ cần kiểm tran_type.bits.is_stab != 0, nếu đúng thìswitchtrênn_type.stab, còn nếu không thì xem các trường khác củan_type.bits. Ngược lại, cũng có thể tạo giá trị như.{ .stab = .gsym }hoặc.{ .bits = .{ .ext = false, .type = .undf, .pext = false, .is_stab = 0 } }Hơi dài dòng về một tính năng ngôn ngữ khác với chủ đề bài gốc, nhưng nếu bạn đang tìm thứ có thể tham khảo cho thiết kế ngôn ngữ mới, thì nên trực tiếp thử
packed structvàpacked unioncủa Zig. Chúng đơn giản nhưng tôi nghĩ là những công cụ khá tốt