Tập 39: Verifier — Cửa ải tối cao

Khi compiler bắt đầu thực hiện các pass tối ưu hóa trên IR (như constant folding hay dead-code elimination), một nguy cơ lớn xuất hiện: bản thân các thuật toán tối ưu có thể bị bug và biến một đoạn IR hợp lệ thành một đoạn IR malformed (lệch kiểu, nhảy vào giữa block, hoặc đọc biến chưa khai báo). Nếu đoạn IR sai này lọt tới backend, compiler sẽ crash hoặc tệ hơn là sinh ra một binary mang hành vi dị thường khi chạy.

Để tự bảo vệ mình, Sprint 38 đưa vào cổng kiểm soát tối cao: IR Verifier.

IR -> [ Verifier ] -> Backend
          |
        Abort! (nếu invariant bị phá vỡ)

Verifier hoạt động như một cảnh sát nội bộ của compiler. Trước khi bàn giao IR cho backend, nó duyệt qua toàn bộ cấu trúc và kiểm tra một danh sách nghiêm ngặt các bất biến (invariants): - Mọi biến trong IR phải được định nghĩa trước khi sử dụng. - Kiểu dữ liệu của các toán hạng trong biểu thức phải hoàn toàn khớp nhau. - Mọi khối cơ bản (basic block) phải kết thúc bằng một lệnh nhảy hoặc return hợp lệ. - Quyền sở hữu và effects không bị vi phạm giữa các ranh giới block.

Tôi nhớ một tối muộn khi tôi đang phát triển tính năng tối ưu hóa hằng số (constant folding) cho IR. Do tính toán nhầm chỉ số thanh ghi tạm, thuật toán của tôi đã sinh ra một lệnh gán đè lên một biến đã được move trước đó. Khi chạy thử, Verifier đã lập tức gầm lên và in ra một màn hình đầy các bất biến bị vi phạm, từ chối cho phép backend LLVM tiếp tục xử lý.

Nếu không có Verifier, đoạn code hỏng đó sẽ được compile thành mã máy, và tôi sẽ phải mất hàng giờ dùng gdb để truy tìm xem tại sao chương trình bị crash ngẫu nhiên lúc chạy. Verifier là cái phao cứu sinh lớn nhất của tôi. Nó biến các bug compiler bí ẩn ở runtime thành các cảnh báo tĩnh rõ ràng ngay khi chạy test, cho phép tôi tự tin refactor các pass tối ưu hóa mà không sợ âm thầm làm hỏng tính đúng đắn của chương trình.


Bình luận (0)

Đang tải bình luận...