Một Lỗi Kỳ Lạ Từ TCC - nói dối e blog

Một Lỗi Kỳ Lạ Từ TCC

TCC (Tiny C Compiler) là một công cụ tuyệt vời, hệ thống hạt của chúng tôi đã tích hợp nó như một module tùy chọn để sinh mã điều khiển hạt theo thời gian thực. So với phiên bản dùng Lua, hiệu năng tăng gấp 10 lần (module thay thế dùng GCC lại đánh đổi mất tính động). Để đưa thư viện này vào sử dụng, tôi còn phải nghiên cứu kỹ giấy phép LGPL nữa.

Vài tuần trước tôi còn phàn nàn TCC không chạy được trên môi trường 64-bit, nhưng hôm nay khi đi tìm lỗi thì phát hiện phiên bản 0.9.25 đã hỗ trợ 64-bit rồi. Khen ngợi đến đây thôi, giờ chuyển sang phần góp ý.

Hai ngày qua, hai đồng nghiệp và tôi đã mắc kẹt trong một lỗi cực kỳ quái dị, phải tạm dừng gần hết các công việc đang làm. Vấn đề phức tạp đến mức gây ra hàng loạt giả thuyết nhưng không ai xác nhận được. Lỗi không xuất phát từ đoạn mã cụ thể nào, lại liên quan đến module render 3D phức tạp - chỉ cần thay đổi chút ít trạng thái ngữ cảnh là lỗi lại nhảy sang đoạn mã hoàn toàn khác.

Đây là lý do tôi luôn cho rằng kiểm thử đơn vị (unit test) hay phát triển hướng kiểm thử không phải là “thuốc tiên”. Thậm chí nhiều khi nó chỉ là trò lừa bịp. Mới hôm qua tôi thấy một dự án trong công ty viết cả núi mã kiểm thử, đọc xong chỉ biết cười trừ. Dùng 1000 dòng test cho module 100 dòng chẳng khác nào lãng phí nhân lực. Thà dùng mắt thường kiểm tra còn hơn. Đến lúc gặp lỗi thật sự thì mấy cái test đó cũng vô dụng, chỉ phát hiện được lỗi chính tả mà thôi.

Thay vì dành thời gian viết bao nhiêu test vô bổ, chi bằng tập trung viết mã thật, nâng cao trình độ để giảm thiểu lỗi.

Quay lại vấn đề chính, cuối cùng chúng tôi đã xác định được nguyên nhân. Khi TCC biên dịch động các hàm có sử dụng thanh ghi dấu phẩy động (floating point), đôi khi nó để lại một con số tồn dư trong ngăn xếp FPU, cụ thể là chiếm giữ st(0).

Trong khi đó, các compiler như GCC/MSVC thường giả định ngăn xếp FPU luôn sạch sẽ trước và sau khi gọi hàm - tức là có sẵn 8 thanh ghi để sử dụng. Ở những trường hợp biên, mã sinh ra sẽ cố gắng lấp đầy cả 8 thanh ghi FPU. Nếu giữa chừng gọi hàm do TCC sinh ra, sẽ xảy ra tràn ngăn xếp FPU, dẫn đến kết quả tính toán sai lệch.

Kết luận thì đơn giản vậy thôi, nhưng cả nhóm mất vài ngày trời vật lộn với đủ kiểu suy đoán và thí nghiệm. Tôi dành cả đêm nghiên cứu source code TCC định viết patch, nhưng quá muộn nên vẫn chưa có đầu绪. Định viết email báo lỗi thì mở file TODO trong gói source ra xem, cả trán toát mồ hôi lạnh - ngay đầu tiên đã ghi rõ:

Bugs:

  • FPU st(0) is left unclean (kwisatz haderach). Incompatible with optimized gcc/msc code

Có cả chuỗi thảo luận liên quan trên mailing list nữa.

Bài học xương máu: Trước khi dùng code nguồn mở, nhất định phải đọc kỹ danh sách lỗi tồn đọng.

0%