LLVM 从入门到迷惑

· · 科技·工程

前言

本教程使用 LLVM 19.1.6,不同版本的 API 可能有所不同,尽量以本教程使用的版本为准。

介绍

LLVM(Low Level Virtual Machine)是一个开源的编译器框架,用于开发编译器、工具链和其他语言的开发工具。它最初由克里斯·拉特纳(Chris Lattner)于2000年在伊利诺伊大学厄巴纳-香槟分校开发,旨在提供一种模块化和可重用的编译器基础设施。

LLVM的核心设计理念是通过一个中间表示(IR,Intermediate Representation)使得不同的语言和优化能够共享同一套工具链。LLVM能够支持多种编程语言的编译过程,并通过优化中间表示来提升程序性能。

ok,套话结束,正式开始。

安装

包管理器

你可以使用包管理器来便捷的安装LLVM,通常执行以下代码就能自动安装。

vcpkg

vcpkg install llvm

msys

pacman -S mingw-x86_64-llvm

如果你使用其他架构,可以将 x86_64 替换为对应架构名,如x86 就对应 i386

xrepo

xrepo install llvm

或者:

xrepo install vcpkg::llvm

自主编译

如果你由于某些原因无法使用包管理器,那么你可以通过 git 拉取源代码仓库并自行编译。

# 拉取源码
git clone https://github.com/llvm/llvm-project.git

# 配置
cd ./llvm-porject
mkdir build
cd ./build

# 编译
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release ../llvm # 你也可以将 Ninja 换成其他的生成器
cmake --build . -- -j$(nproc)
cmake --install .

由于 LLVM 过于庞大,你可能需要等待非常长的时间,如果不是特殊要求,建议使用包管理器。

配置

你可以向你的 Cmake 项目配置文件中添加以下语句:

find_package(LLVM REQUIRED CONFIG)
target_link_libraries(<target-name> PRIVATE LLVM)

<target-name> 替换为你的可执行程序项目目标。

一个完整的例子如下

cmake_minimum_required(VERSION 3.11)

# 设置项目名称
project(MyProject)

# 寻找 LLVM 包
find_package(LLVM REQUIRED CONFIG)

# 设置 C++ 标准(可选)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 定义可执行文件 main
add_executable(main main.cpp)

# 将 LLVM 链接到 main 目标
target_link_libraries(main PRIVATE LLVMCore)

当你完成以上工作,配置阶段就结束了,接下来步入正式部分。

相关类

llvm::LLVMContext

这是一个上下文类,它管理你所使用的 LLVM IR,这个类非常重要。

通常我们在整个生成过程中只创建一个 LLVMContext 对象,如果你需要使用多个上下文,注意不要混用使用不同上下文声明的 llvm 相关类。

该类不可复制,建议使用指针持有或使用 std::move() 来管理。

llvm::Module

这是一个模块,类似 C++ 中的一个翻译单元。

所有的 IR 指令都由该类保存。

构造函数

Module(StringRef ModuleID, LLVMContext &Context);

这里的 StringRef 类似 std::string_view ,它并不分配内存而是对已有字符串的引用,你可以直接将其看成 std::string 来使用。

Module(StringRef ModuleID, LLVMContext &Context, const TargetTriple &Triple, const DataLayout &Layout);

llvm::IRBuilder<T>

是一个工具类,用于对常用指令的快速生成和插入,不可复制。

构造函数

IRBuilder 需要一个 插入点(Insert Point),即在哪个基本块中插入新的指令。这个插入点通常是 BasicBlock 的一个迭代器。

IRBuilder(LLVMContext &Context);
IRBuilder(LLVMContext &Context, BasicBlock *B);
IRBuilder(LLVMContext &Context, Instruction *InsertBefore);

该类的 T 类型实际上是对于 IRBuilder 的优化进行一些限制的选项,例如:

llvm::IRBuilder<llvm::NoFolder> Builder(Context);

可以禁止 IRBuilder 自动合并指令。这在某些优化场景下可能会用到。

以下是 IRBuilder 可选的参数:

编写 LLVM IR

函数

创建函数签名

为了创建一个函数,我们需要先创建一个函数签名。

llvm::FunctionType* funcType = llvm::FunctionType::get(<returnType>, <argTypes>, <isVarArg>);

示例:

llvm::FunctionType* funcType = llvm::FunctionType::get(llvm::Type::getInt32Ty(context), {llvm::Type::getInt32Ty(context)}, false);

创建函数

之后就可以使用函数签名来创建一个函数了:

llvm::Function* func = llvm::Function::Create(<funcType>, <linkType>, <funcName>, <module>);

示例:

llvm::Function* func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "my_function", module);

创建基本块

每个基本块都保存着一系列指令,你可以通过 brcondbr 命令在不同块之间进行跳转,但是注意不能有环。

llvm::BasicBlock* entry = llvm::BasicBlock::Create(<context>, <name>, <func>);

基本块在函数中的顺序是按照定义顺序来的。如果你不能保证定义顺序,请在每个基本块的末尾加入控制流指令。

示例:

llvm::BasicBlock* entry = llvm::BasicBlock::Create(context, "entry", func);