Tác giả dxil-spirv viết lại bộ tái cấu trúc luồng điều khiển để xử lý shader phức tạp
Sau nhiều năm vá lỗi cho một bộ tái cấu trúc luồng điều khiển khó bảo trì, tác giả dxil-spirv đã thay thế phần cốt lõi bằng thuật toán dựa trên cách Mesa xử lý luồng điều khiển không cấu trúc. Phiên bản mới vượt qua kiểm tra hợp lệ SPIR-V trên toàn bộ kho shader DXIL mà tác giả tích lũy trong sáu năm, trong khi hiệu năng GPU được đánh giá là có vẻ tốt.

Tác giả dxil-spirv đã thay thế bộ tái cấu trúc luồng điều khiển (control-flow structurizer) cũ bằng một cách tiếp cận mới, tập trung vào tính đúng đắn và khả năng xử lý các shader phức tạp. Thay đổi này giúp toàn bộ kho shader DXIL mà tác giả kiểm tra vượt qua xác thực SPIR-V ngay lần đầu, đồng thời hiệu năng GPU ban đầu cho thấy kết quả khả quan.
Thay thế nền tảng khó bảo trì
Bộ tái cấu trúc cũ đã gây khó khăn trong nhiều năm. Những thay đổi nhỏ từ trò chơi cũng có thể làm lộ lỗi, buộc tác giả liên tục sửa từng vấn đề phát sinh. Một shader đặc biệt phức tạp từng mất vài ngày để khắc phục; đồ thị luồng điều khiển của nó còn quá lớn để Graphviz dựng hình trong thời gian hữu hạn.
Theo tác giả, sai lầm lớn nhất là cố giải quyết mọi trường hợp trong dxil-spirv mà vẫn giữ mã được sinh ra dễ đọc. Các phương pháp dựa trên heuristic ngày càng chồng chất, khiến phần triển khai trở thành một khối mã khoảng 9.000 dòng khó suy luận và khó bảo trì.
Thay vì tiếp tục ưu tiên khả năng đọc của mã đầu ra, tác giả chuyển trọng tâm sang tính đúng đắn, đồng thời cố gắng duy trì hiệu năng. Hướng triển khai mới dựa trên cách Mesa xử lý luồng điều khiển không cấu trúc, vốn được OpenCL sử dụng vì luồng điều khiển trong CL SPIR-V không có cấu trúc.
Thử nghiệm trên shader đồ họa
Để kiểm tra cách làm của Mesa, tác giả bổ sung một nhánh trong dxil-spirv nhằm bỏ qua hoàn toàn bước tái cấu trúc và chuyển luồng điều khiển nguyên bản đến trình điều khiển. Thử nghiệm sử dụng chuỗi phần mở rộng SPIR-V giả SPV_FROG_unstructured_control_flow để kích hoạt đường xử lý của OpenCL trong Mesa, sau đó điều chỉnh một số phần để tương thích với shader đồ họa.
Khi thử trên một số trò chơi, kết quả ban đầu không làm hệ thống gặp sự cố. Hiệu năng ngang bằng trên RADV, còn hiệu năng với trình điều khiển độc quyền của NVIDIA vẫn là dấu hỏi do cách hoạt động của thuật toán. Tác giả chấp nhận đánh cược hai tuần để tiếp tục với hướng đi này.
Chuyển nhánh và xử lý biến
Thuật toán mới dựa trên việc duyệt đồ thị luồng điều khiển (control-flow graph, CFG) theo cách đệ quy. Khi một nhánh cần đi đến khối mã nằm ngoài phạm vi hiện tại, thuật toán có thể ghi lại đường đi dự định bằng các biến lựa chọn, rồi chuyển luồng đến một khối hợp nhất bên ngoài để định tuyến tiếp.
Việc xử lý các khối rẽ nhánh nhiều hướng cũng được đơn giản hóa: thay vì cố giữ nguyên cấu trúc switch, mã sẽ tách chúng thành chuỗi nhánh điều kiện, tương tự cách Mesa thực hiện.
Một thay đổi quan trọng khác là từ bỏ việc cố bảo toàn nút PHI trong dạng SSA qua toàn bộ quá trình viết lại. Nút PHI thể hiện giá trị được chọn từ các nhánh khác nhau trong biểu diễn trung gian; duy trì chúng trong CFG biến đổi đã tạo ra độ phức tạp và một số lỗi khó phát hiện. Thay vào đó, dxil-spirv chuyển PHI thành biến thông thường với các thao tác lưu và nạp. Tác giả cho rằng các trình biên dịch có thể tự tối ưu hóa bước này.
Fuzzer — công cụ tự động tạo các trường hợp kiểm thử — bắt đầu vượt qua những mẫu luồng điều khiển rất phức tạp. Trong quá trình xử lý vòng lặp, công cụ này tìm ra khoảng 20 lỗi. Phiên bản mới cũng bảo đảm khả năng hội tụ lại tối đa (maximal reconvergence): với mỗi khối được thực thi, nhóm luồng lớn nhất có thể sẽ cùng thực hiện đường đi đó. Đặc tính này không được bảo đảm trong mọi trường hợp ở bộ triển khai cũ.
Vòng lặp lồng nhau là thử thách lớn nhất
Vòng lặp khiến bài toán khó hơn đáng kể. Với luồng điều khiển không cấu trúc, một khối có thể thoát khỏi nhiều vòng lặp hoặc tiếp tục ở một vòng lặp bên ngoài. Trong khi đó, SPIR-V không cho phép một lệnh thoát khỏi nhiều vòng lặp cùng lúc hoặc tiếp tục trực tiếp ở một vòng lặp xa hơn.
Để xử lý, thuật toán theo dõi ngăn xếp các phạm vi vòng lặp, ghi nhận những thao tác thoát hoặc tiếp tục cần được chuyển tiếp. Khi gặp trường hợp thoát nhiều tầng, các biến lựa chọn được dùng để xử lý từng tầng lần lượt. Một số nhánh cần thêm khối trung gian để giữ cho quá trình duyệt đệ quy hoạt động đúng.
Tác giả mô tả các vòng lặp lồng nhau là phần khó khăn nhất, nhưng cho biết fuzzer đã tạo ra nhiều trường hợp cực đoan để kiểm tra. Sau nhiều vòng sửa và thử nghiệm, cách triển khai mới xử lý được cả những cấu trúc có nhiều tầng thoát và tiếp tục.
Kho shader vượt qua xác thực SPIR-V
Tác giả chạy phiên bản mới trên kho shader DXIL đã tích lũy trong sáu năm. Toàn bộ kho vượt qua xác thực SPIR-V ngay lần đầu. Hiệu năng GPU cũng có vẻ tốt, dù bài viết không đưa ra số liệu đo cụ thể.
Kết quả này chưa chứng minh mọi trường hợp phát sinh trong tương lai đều đã được xử lý, nhưng cho thấy việc thay thế bộ triển khai cũ đã giải quyết được một nút thắt lớn trong quá trình biên dịch shader của dxil-spirv.
Góc nhìn
Với các nhóm phát triển trình biên dịch và công cụ đồ họa, trường hợp này cho thấy việc ưu tiên tính đúng đắn và đơn giản hóa cách biểu diễn có thể hữu ích hơn việc cố giữ mã đầu ra dễ đọc bằng nhiều heuristic. Những biến trung gian và bước hạ cấp biểu diễn tưởng như kém thanh lịch vẫn có thể phù hợp nếu trình biên dịch phía sau tối ưu hóa được chúng.
Fuzzer cũng đóng vai trò thiết yếu khi xử lý CFG phức tạp: nó giúp phát hiện những lỗi khó tái tạo bằng kiểm thử thông thường. Tuy vậy, kết quả hiệu năng trong bài viết mới chỉ được mô tả là khả quan, nên chưa thể thay thế các phép đo chi tiết trên nhiều trình điều khiển và khối lượng công việc khác nhau.
Nguồn: themaister.net · Quy trình biên tập

