LLVM: Những vấn đề tồn tại
(npopov.com)- Phân tích đa chiều những giới hạn mang tính cấu trúc và nợ kỹ thuật của dự án LLVM, đồng thời chỉ rõ các khu vực cần được cải thiện
- Nêu ra các điểm nghẽn trong vận hành một dự án mã nguồn mở quy mô lớn như thiếu review, API thiếu ổn định, thời gian build và biên dịch, CI thiếu ổn định
- Các vấn đề trong thiết kế IR bao gồm xử lý giá trị undef, mã hóa ràng buộc, ngữ nghĩa số thực dấu chấm động, và sự không đầy đủ của đặc tả
- Chỉ ra các vấn đề cấu trúc dài hạn như tính dị biệt giữa các backend, sự lẫn lộn trong xử lý ABI, việc chậm chuyển đổi sang GlobalISel và pass manager
- Thay vì phủ nhận hiện trạng của LLVM, bài viết xem đây là cơ hội để cải tiến liên tục và mở rộng đóng góp
Các vấn đề cấu trúc chính
-
Thiếu năng lực review được xem là nút thắt lớn nhất
- Có nhiều người viết mã nhưng thiếu reviewer, dẫn đến các trường hợp thay đổi chưa được kiểm chứng vẫn được hợp nhất
- Do cấu trúc mà việc yêu cầu review là trách nhiệm của tác giả, người đóng góp mới khó tìm được reviewer phù hợp
- Việc đưa vào hệ thống tự động phân công PR của Rust được nhắc đến như một hướng cải thiện
-
API và IR thay đổi thường xuyên (churn) gây gánh nặng cho người dùng
- C API tương đối ổn định, nhưng C++ API thay đổi thường xuyên, làm tăng chi phí bảo trì frontend và backend
- Triết lý “Upstream or GTFO” khiến mã không được chia sẻ không được phản ánh trong quá trình ra quyết định
-
Vấn đề thời gian build quá lớn
- LLVM gồm hơn 2,5 triệu dòng mã C++, nên thời gian build rất dài; khi build debug thì mức sử dụng bộ nhớ và dung lượng đĩa tăng vọt
- Các hướng cải thiện như precompiled header (PCH), build mặc định dưới dạng dylib, và daemon hóa kiểm thử đang được thảo luận
-
CI thiếu ổn định
- Hơn 200 buildbot kiểm thử trên nhiều môi trường khác nhau, nhưng không thể luôn duy trì trạng thái “xanh”
- Test flaky và vấn đề với buildbot làm tín hiệu cảnh báo bị pha loãng, khiến việc phát hiện lỗi thực sự trở nên khó khăn
- Việc áp dụng kiểm thử trước PR đã cải thiện phần nào, nhưng vẫn chưa giải quyết tận gốc
-
Thiếu kiểm thử end-to-end
- Các unit test cho từng tối ưu hóa riêng lẻ khá đầy đủ, nhưng gần như không có kiểm thử cho toàn bộ pipeline hoặc cho sự kết hợp với backend
llvm-test-suitecó tồn tại, nhưng chưa bao phủ đầy đủ các tổ hợp toán tử cơ bản và kiểu dữ liệu
Các vấn đề liên quan đến backend và hiệu năng
-
Tính dị biệt giữa các backend
- Dù các giai đoạn trung gian được hợp nhất, các backend vẫn có nhiều chỉnh sửa độc lập theo từng target, làm tăng trùng lặp và phân nhánh
- Có xu hướng thêm hook dành riêng cho target thay vì tối ưu hóa dùng chung
-
Thời gian biên dịch
- Chậm trong JIT và ở các ngôn ngữ tạo IR quy mô lớn như Rust, C++
- Build với
-O0đặc biệt chậm, và backend TPDE được nêu như một phương án thay thế nhanh hơn tới 10~20 lần
-
Thiếu theo dõi hiệu năng
- Không có hạ tầng theo dõi hiệu năng runtime chính thức
- Hệ thống LNT có hiệu quả thấp do hoạt động thiếu ổn định, UX kém và thiếu dữ liệu
Các vấn đề trong thiết kế IR
-
Độ phức tạp trong xử lý giá trị undef
- Mỗi lần sử dụng có thể mang một giá trị khác nhau, nên dễ gây lỗi trong quá trình tối ưu hóa
- Về sau có thể được thay thế bằng giá trị poison, nhưng việc xử lý poison trong bộ nhớ vẫn còn chưa hoàn thiện
-
Đặc tả không đầy đủ và thiếu nhất quán
- Tồn tại các trường hợp miscompile cũ vẫn chưa được giải quyết
- Có những bài toán thiết kế khó như mô hình provenance
- Để xử lý việc này, một nhóm công tác về đặc tả chính thức đã được thành lập
-
Thiếu tính nhất quán trong cách mã hóa ràng buộc
- Nhiều cách khác nhau cùng tồn tại như cờ poison, metadata, attribute, assumes
- Điều này ảnh hưởng tiêu cực đến tối ưu hóa do làm mất thông tin hoặc giữ lại quá nhiều thông tin
-
Vấn đề về ngữ nghĩa số thực dấu chấm động (FP)
- Xuất hiện sự không nhất quán ở signaling NaN, môi trường phi chuẩn, xử lý denormal, độ chính xác vượt mức của x87
- Việc xử lý riêng bằng FP intrinsic có ràng buộc làm tăng thêm độ phức tạp
Các vấn đề kỹ thuật khác
-
Sự chậm trễ trong các đợt di trú từng phần
- Pass manager mới mới chỉ áp dụng đến giai đoạn trung gian, còn backend vẫn dùng bản cũ
- GlobalISel sau 10 năm vẫn chưa chuyển đổi hoàn toàn, vẫn song song tồn tại với SDAG
-
Sự lẫn lộn trong xử lý ABI và calling convention
- Việc phân chia trách nhiệm giữa frontend và backend không rõ ràng, lại thiếu tài liệu
- Việc đưa vào thư viện ABI cùng các bản triển khai nguyên mẫu đang được tiến hành
- Có vấn đề là ABI thay đổi tùy theo việc bật các tính năng của target
-
Quản lý hàm builtin và libcalls thiếu nhất quán
- TargetLibraryInfo và RuntimeLibcalls bị tách rời nên thiếu tính nhất quán
- Không thể nhận biết tính khả dụng theo từng loại thư viện runtime (libgcc, compiler-rt, v.v.)
- Thiếu điểm tùy biến cho runtime bên ngoài như Rust
-
Cấu trúc Context / Module kém hiệu quả
- Kiểu và hằng số nằm trong Context, còn hàm và biến global nằm trong Module
- Không thể truy cập data layout nên gây bất tiện cho các tác vụ như constant folding
- Không thể liên kết chéo giữa các context, cần đơn giản hóa cấu trúc
-
Áp lực thanh ghi do LICM (loop-invariant code motion)
- Thực hiện hoist vô điều kiện mà không có cost model
- Backend không sink lại, dẫn đến tăng spill và reload
Kết luận
- Các vấn đề được liệt kê là những thách thức cấu trúc xuất phát từ độ trưởng thành và quy mô của LLVM, đồng thời được xem là cơ hội để cải thiện chất lượng dự án và trải nghiệm của người đóng góp
- Một số mảng như tối ưu hóa build, thư viện ABI, theo dõi hiệu năng đã có các công việc cải thiện đang diễn ra
- Nhìn chung, LLVM vẫn rất mạnh, nhưng việc refactor liên tục và hoàn thiện đặc tả là điều bắt buộc
1 bình luận
Ý kiến trên Hacker News
Bài viết được sắp xếp rất tốt nên tôi rất đồng cảm.
Dạo này độ ổn định của LLVM IR đã cao lên đáng kể. Tôi đã rebase Fil-C từ LLVM 17 lên 20 chỉ trong một ngày.
Ở các dự án khác tôi cũng đã duy trì cùng một pass trên nhiều phiên bản LLVM mà không gặp vấn đề lớn nào.
Tuy vậy, áp lực thanh ghi của LICM đặc biệt nghiêm trọng với các nguồn không phải C/C++. Có vẻ vấn đề không nằm ở bản thân LICM mà là regalloc cần học cách rematerialize tốt hơn
Sẽ tốt hơn nếu mở thêm các tùy chọn để lập trình viên frontend có thể benchmark nhiều cấu hình khác nhau và chọn giá trị tối ưu
Mỗi công cụ và thành phần đều có quy tắc riêng, nên việc xuất hiện khác biệt giữa các phiên bản có lẽ lại là điều tự nhiên. Tôi vẫn băn khoăn không biết mình có hiểu sai không
Tôi từng nhờ người phụ trách compiler-rt đổi đúng một boolean để có thể build LLVM 18 trên macOS, nhưng issue bị khóa vì trở nên “heated” và 4 năm rồi vẫn chưa được giải quyết.
Dù vậy tôi vẫn rất yêu LLVM. clang-tidy, ASAN, UBSAN, LSAN, MSAN, TSAN thực sự rất xuất sắc.
Tôi nghĩ viết mã C/C++ mà không dùng clang-tidy là một lựa chọn sai lầm.
Nhưng -fbounds-safety chỉ có trong AppleClang, còn MSAN/LSAN chỉ có trong LLVM Clang. Xcode thì cũng không có clang-tidy, clang-format hay llvm-symbolizer.
Cuối cùng trên macOS tôi vẫn phải tự build Darwin LLVM để dùng.
Phía Linux cũng khá rối. RHEL không cung cấp libcxx nhưng Fedora thì có. Tuy nhiên không có bản phân phối nào có libcxx được instrument cho MSAN.
Fedora gần như đã tới đích, nhưng vẫn phải tự build compiler-rt bằng tay
Sau khi đọc các thảo luận gần đây về LLVM, tôi cảm thấy thực sự cần một test suite thực thi được bắt đầu từ LLVM IR thay vì từ C.
Khi tự làm backend, tài liệu về SelectionDAG hay GlobalISel khá thiếu, ý nghĩa của các phép toán cũng không rõ ràng nên rất dễ triển khai sai
C API có cảm giác như một phần bị bỏ quên trong LLVM. Nhiều tùy chọn hay opt pass không được phơi bày ra
Vì đa số lập trình viên dùng trực tiếp C++ API, nên C API bị đẩy xuống ưu tiên thấp hơn và cuối cùng trở thành công dân hạng hai
Code review không mang lại phần thưởng tức thời nên mọi người thường không muốn làm
Tôi nghĩ nếu review cũng được tính credit đóng góp thì sẽ tạo thêm động lực
6 năm trước tôi thường xuyên build LLVM trên laptop Dell 9360 8GB. Nếu giảm mức song song khi link thì vẫn làm được trong giới hạn bộ nhớ.
Tôi tò mò không biết giờ 8GB còn build được không
Thời kỳ đầu LLVM có lợi thế là tốc độ biên dịch nhanh hơn GCC.
Giờ sau 23 năm của LLVM, tôi tự hỏi liệu sẽ còn thứ gì mới xuất hiện nữa không
Cũng có các lựa chọn thay thế như Cranelift không dùng LLVM IR (Cranelift GitHub)
Xử lý ABI và calling convention là nỗi đau lớn nhất.
Frontend của compiler phải tự quản lý việc truyền tham số, đôi khi còn phải tự tính số lượng thanh ghi
Bài viết nói rằng “frontend được bảo vệ nhờ C API ổn định”, nhưng thực tế không hẳn vậy
Một số API thì ổn định, nhưng các phần như Orc lại thay đổi thường xuyên
LLVM có vẻ gần như không có quy trình review issue. Các bug report tôi gửi lên cũng nhiều năm rồi vẫn chưa được xử lý