Mục Lục
- Giới thiệu
- Yêu cầu và Thiết lập Môi trường
- Bước 1: Biên dịch LLVM từ Mã nguồn
- Bước 2: Viết Pass Hello World cho LLVM
- Bước 3: Biên dịch Pass
- Bước 4: Chạy Pass
- Kết luận
- Câu hỏi thường gặp
Giới thiệu
Nếu bạn từng tò mò về cách hoạt động của các trình biên dịch, LLVM là một trong những sân chơi tốt nhất mà bạn có thể tìm thấy. Nó mạnh mẽ, linh hoạt và được sử dụng rộng rãi từ các ngôn ngữ lập trình đến các trình điều khiển GPU. Tuy nhiên, LLVM khá lớn và việc bắt đầu có thể khiến bạn cảm thấy choáng ngợp.
Một điểm khởi đầu tuyệt vời là với các pass của LLVM. Chúng là các khối xây dựng của tối ưu hóa trình biên dịch và phân tích chương trình. Bằng cách tạo ra các pass của riêng bạn và thực nghiệm với IR, bạn sẽ nhanh chóng nắm bắt được những khái niệm cốt lõi làm cho LLVM hoạt động.
Bài viết này sẽ hướng dẫn bạn từ việc xây dựng LLVM đến việc chạy một pass "hello world" đơn giản — bước đầu tiên của bạn vào thế giới nội bộ của trình biên dịch.
Yêu cầu và Thiết lập Môi trường
Trước khi bắt đầu, hãy đảm bảo rằng bạn đã cài đặt các công cụ cần thiết. Trên Ubuntu/Debian, bạn có thể cài đặt chúng bằng lệnh sau:
bash
sudo apt -y install gcc g++ git cmake ccache ninja-build zlib1g-dev
Chúng ta cũng cần Git để clone mã nguồn của LLVM, và Ccache để giảm thời gian biên dịch của LLVM.
Bước 1: Biên dịch LLVM từ Mã nguồn
Thực hiện theo các bước sau để clone, cấu hình và biên dịch LLVM (dựa trên hướng dẫn chính thức của LLVM và kinh nghiệm của chúng tôi):
bash
git clone https://github.com/llvm/llvm-project.git
cd llvm-project/
git checkout -b llvm-17 llvmorg-17.0.1
mkdir build
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \
-DCMAKE_EXPORT_COMPILE_COMMANDS=TRUE \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DLLVM_ENABLE_PROJECTS=clang \
-DCMAKE_INSTALL_PREFIX=/opt/llvm \
-B build -S llvm
cmake --build build -j$(nproc)
cmake --build build --target check-all -j$(nproc)
👉 check-all chạy bộ kiểm tra của LLVM. Nếu tất cả các bài kiểm tra đều vượt qua, bạn đã sẵn sàng.
👉 -DBUILD_SHARED_LIBS giúp tránh các vấn đề khi chạy pass của bạn với opt.
Để cài đặt LLVM vào /opt/llvm, hãy chạy:
bash
cmake --install build
Tại thời điểm này, bạn đã có một phiên bản LLVM + Clang hoạt động.
Bước 2: Viết Pass Hello World cho LLVM
Bây giờ, hãy viết một dummy pass đơn giản. Pass này sẽ không thay đổi chương trình — nó chỉ in ra một thông điệp mỗi khi nó chạy.
Tạo một tệp mới hellopass.cpp với nội dung sau:
cpp
#include "llvm/IR/Function.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct HelloWorldPass : PassInfoMixin<HelloWorldPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
errs() << "Hello from: " << F.getName() << "\n";
return PreservedAnalyses::all();
}
};
} // namespace
// Đăng ký pass
extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "HelloWorldPass", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "hello-world") {
FPM.addPass(HelloWorldPass());
return true;
}
return false;
});
}
};
}
Các bài viết sau sẽ đi sâu vào giải thích về các pass. Hãy nhớ rằng, về cơ bản, chúng ta có hai "phần/trách nhiệm" trong đoạn mã trên: Logic của pass và việc đăng ký nó. Phương thức run là nơi logic của bạn xảy ra. Ở đây, chúng ta chỉ in ra tên hàm.
Bước 3: Biên dịch Pass
Sử dụng CMake để biên dịch pass thành một thư viện chia sẻ. Tạo một tệp CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.13)
project(HelloPass LANGUAGES CXX)
find_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
add_library(HelloPass MODULE hellopass.cpp)
# Đảm bảo sử dụng C++17
set_target_properties(HelloPass PROPERTIES CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES)
Sau đó biên dịch:
bash
mkdir build && cd build
cmake -DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DCMAKE_CXX_COMPILER='clang++' ..
cmake --build .
Kết quả sẽ tạo ra libHelloPass.so.
Bước 4: Chạy Pass
Bây giờ, hãy biên dịch một tệp C++ đơn giản thành LLVM IR:
cpp
// test.cc
int add(int a, int b) {
return a + b;
}
Biên dịch thành LLVM IR:
bash
/opt/llvm/bin/clang++ -S -emit-llvm test.cc -o test.ll
Chạy pass:
bash
/opt/llvm/bin/opt -load-pass-plugin=./libHelloPass.so -passes=hello-world -disable-output < test.ll
Bạn sẽ thấy đầu ra như sau:
plaintext
Hello from: add
Kết luận
Bạn đã:
- Biên dịch LLVM từ mã nguồn
- Thực hiện pass LLVM đầu tiên của bạn
Từ đây, bạn có thể mở rộng pass dummy để phân tích luồng điều khiển, tối ưu hóa các lệnh hoặc thậm chí biến đổi IR. Bước "Hello World" này là cánh cửa vào phát triển trình biên dịch thực sự.
Câu hỏi thường gặp
1. LLVM là gì?
LLVM là một dự án mã nguồn mở cho phép phát triển các trình biên dịch và công cụ tối ưu hóa.
2. Tôi cần kiến thức gì trước khi bắt đầu?
Kiến thức cơ bản về C++ và trình biên dịch là hữu ích.
3. Pass trong LLVM là gì?
Pass là các khối xây dựng của tối ưu hóa và phân tích trong trình biên dịch.
4. Làm thế nào để cài đặt LLVM trên hệ điều hành khác?
Bạn có thể tham khảo tài liệu chính thức của LLVM cho từng hệ điều hành.
5. Tôi có thể phát triển các pass phức tạp hơn không?
Có, bạn có thể mở rộng và phát triển các pass với nhiều chức năng hơn.
Thực hành tốt nhất
- Đảm bảo sử dụng phiên bản LLVM mới nhất để tránh lỗi.
- Tạo ra nhiều test case để kiểm tra pass của bạn.
- Tham gia vào cộng đồng LLVM để học hỏi và chia sẻ kinh nghiệm.
Cạm bẫy phổ biến
- Không kiểm tra các lỗi biên dịch có thể xảy ra khi viết pass.
- Quên đăng ký pass của bạn đúng cách, dẫn đến không chạy được.
Mẹo hiệu suất
- Sử dụng
ccacheđể giảm thời gian biên dịch. - Tối ưu hóa mã của bạn bằng cách sử dụng các công cụ phân tích hiệu suất để tìm các điểm nghẽn.
Khắc phục sự cố
- Nếu pass không chạy, kiểm tra lại đăng ký pass và cách bạn biên dịch nó.
- Kiểm tra các thông báo lỗi từ LLVM để hiểu rõ hơn về nguyên nhân.