使用LLVM IR构建C++ AOT编译器的核心是利用LLVM提供的中间表示层和后端工具链,将自定义语言的源代码转换为可执行的机器码。整个过程不需要从零实现指令优化和目标码生成,大幅降低开发复杂度。

核心开发流程概述
完整的编译器构建流程可以分为四个核心阶段,每个阶段对应不同的功能模块:
- 前端解析:完成词法分析、语法分析,生成抽象语法树(AST)
- IR生成:遍历AST,转换为对应的LLVM IR指令
- 优化处理:调用LLVM内置的优化管线提升代码执行效率
- 目标码生成:将优化后的IR编译为对应平台的机器码,输出可执行文件
前置环境准备
首先需要安装LLVM开发库,以Ubuntu系统为例,执行以下命令安装依赖:
# 安装LLVM开发包 sudo apt-get install llvm-dev clang # 验证安装版本 llvm-config --version
项目中需要链接LLVM的相关库,CMake配置示例如下:
cmake_minimum_required(VERSION 3.10)
project(MyCompiler)
# 查找LLVM包
find_package(LLVM REQUIRED CONFIG)
# 包含LLVM头文件
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
# 添加可执行文件
add_executable(my_compiler main.cpp)
# 链接LLVM库
llvm_map_components_to_libnames(llvm_libs core support irreader executionengine)
target_link_libraries(my_compiler ${llvm_libs})
前端解析实现
前端负责将源代码转换为AST,这里以简单的算术表达式语言为例,实现基础的词法和语法分析。首先定义词法单元类型:
#include <string>
#include <vector>
#include <memory>
// 词法单元类型枚举
enum TokenType {
TOKEN_NUMBER,
TOKEN_PLUS,
TOKEN_MINUS,
TOKEN_EOF
};
// 词法单元结构体
struct Token {
TokenType type;
std::string value;
};
// 词法分析器
class Lexer {
private:
std::string input;
size_t pos;
public:
Lexer(const std::string& in) : input(in), pos(0) {}
Token getNextToken() {
// 跳过空白字符
while (pos < input.size() && isspace(input[pos])) {
pos++;
}
if (pos >= input.size()) {
return {TOKEN_EOF, ""};
}
char current = input[pos];
if (isdigit(current)) {
std::string num;
while (pos < input.size() && isdigit(input[pos])) {
num += input[pos++];
}
return {TOKEN_NUMBER, num};
} else if (current == '+') {
pos++;
return {TOKEN_PLUS, "+"};
} else if (current == '-') {
pos++;
return {TOKEN_MINUS, "-"};
}
// 简单处理错误,实际开发需要更完善的错误提示
return {TOKEN_EOF, ""};
}
};
接下来实现语法分析器,生成表达式对应的AST节点:
// AST节点基类
class ASTNode {
public:
virtual ~ASTNode() = default;
virtual void* codegen() = 0; // 后续用于生成LLVM IR
};
// 数字节点
class NumberNode : public ASTNode {
public:
int value;
NumberNode(int v) : value(v) {}
void* codegen() override;
};
// 二元运算节点
class BinaryOpNode : public ASTNode {
public:
TokenType op;
std::unique_ptr<ASTNode> left;
std::unique_ptr<ASTNode> right;
BinaryOpNode(TokenType o, std::unique_ptr<ASTNode> l, std::unique_ptr<ASTNode> r)
: op(o), left(std::move(l)), right(std::move(r)) {}
void* codegen() override;
};
// 语法分析器
class Parser {
private:
Lexer lexer;
Token currentToken;
void eat(TokenType type) {
if (currentToken.type == type) {
currentToken = lexer.getNextToken();
}
}
public:
Parser(Lexer l) : lexer(l) {
currentToken = lexer.getNextToken();
}
std::unique_ptr<ASTNode> parseNumber() {
if (currentToken.type == TOKEN_NUMBER) {
int val = std::stoi(currentToken.value);
eat(TOKEN_NUMBER);
return std::make_unique<NumberNode>(val);
}
return nullptr;
}
std::unique_ptr<ASTNode> parseExpression() {
auto node = parseNumber();
if (!node) return nullptr;
while (currentToken.type == TOKEN_PLUS || currentToken.type == TOKEN_MINUS) {
TokenType op = currentToken.type;
if (op == TOKEN_PLUS) {
eat(TOKEN_PLUS);
} else {
eat(TOKEN_MINUS);
}
auto right = parseNumber();
if (!right) return nullptr;
node = std::make_unique<BinaryOpNode>(op, std::move(node), std::move(right));
}
return node;
}
};
LLVM IR生成实现
遍历AST生成LLVM IR,需要引入LLVM的相关头文件,初始化LLVM上下文和模块:
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/Verifier.h>
using namespace llvm;
// 全局LLVM上下文
static LLVMContext Context;
// 全局IR构建器
static IRBuilder<> Builder(Context);
// 全局模块
static std::unique_ptr<Module> Mod;
// 数字节点IR生成实现
void* NumberNode::codegen() {
// 生成常量整数值
return ConstantInt::get(Context, APInt(32, value, true));
}
// 二元运算节点IR生成实现
void* BinaryOpNode::codegen() {
Value* leftVal = static_cast<Value*>(left->codegen());
Value* rightVal = static_cast<Value*>(right->codegen());
if (!leftVal || !rightVal) return nullptr;
if (op == TOKEN_PLUS) {
return Builder.CreateAdd(leftVal, rightVal, "addtmp");
} else if (op == TOKEN_MINUS) {
return Builder.CreateSub(leftVal, rightVal, "subtmp");
}
return nullptr;
}
还需要实现一个函数包装器,将表达式计算逻辑封装为可执行的LLVM函数:
// 创建主函数,包装表达式计算结果
Function* createMainFunction(ASTNode* ast) {
// 定义函数类型:返回32位整数,无参数
FunctionType* funcType = FunctionType::get(Builder.getInt32Ty(), false);
// 创建函数
Function* mainFunc = Function::Create(funcType, Function::ExternalLinkage, "main", Mod.get());
// 创建基本块
BasicBlock* bb = BasicBlock::Create(Context, "entry", mainFunc);
Builder.SetInsertPoint(bb);
// 生成表达式IR
Value* exprVal = static_cast<Value*>(ast->codegen());
if (!exprVal) return nullptr;
// 返回表达式结果
Builder.CreateRet(exprVal);
// 验证函数合法性
if (verifyFunction(*mainFunc)) {
return nullptr;
}
return mainFunc;
}
优化与目标码生成
生成IR后,可以调用LLVM的优化管线提升代码质量,再将IR编译为目标平台的机器码:
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/Transforms/InstCombine/InstCombine.h>
#include <llvm/Transforms/Scalar.h>
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Support/raw_ostream.h>
// 初始化LLVM目标平台
void initLLVMTargets() {
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();
InitializeNativeTargetAsmParser();
}
// 执行IR优化
void optimizeModule() {
legacy::PassManager pm;
// 添加基础优化pass
pm.add(createInstructionCombiningPass());
pm.add(createReassociatePass());
pm.add(createGVNPass());
pm.add(createCFGSimplificationPass());
pm.run(*Mod);
}
// 生成目标文件
bool generateObjectFile(const std::string& outputPath) {
std::string error;
const Target* target = TargetRegistry::lookupTarget("", error);
if (!target) {
return false;
}
TargetOptions opt;
std::unique_ptr<TargetMachine> targetMachine(
target->createTargetMachine(
"", // 目标三元组,空字符串表示当前平台
"", // CPU类型
"", // 特性列表
opt,
None,
None,
CodeGenOpt::Default
)
);
Mod->setDataLayout(targetMachine->createDataLayout());
Mod->setTargetTriple("");
// 输出目标文件
std::error_code ec;
raw_fd_ostream dest(outputPath, ec, sys::fs::OF_None);
if (ec) {
return false;
}
legacy::PassManager pass;
if (targetMachine->addPassesToEmitFile(pass, dest, nullptr, CodeGenFileType::CGFT_ObjectFile)) {
return false;
}
pass.run(*Mod);
dest.flush();
return true;
}
完整主程序示例
将上述模块整合,实现完整的编译器主逻辑:
int main(int argc, char* argv[]) {
if (argc != 3) {
// 参数:输入源代码文件,输出目标文件路径
return 1;
}
// 读取源代码
std::string source;
// 这里省略文件读取逻辑,假设source为输入的表达式字符串,比如"1+2-3"
source = "1+2-3";
// 初始化LLVM
initLLVMTargets();
Mod = std::make_unique<Module>("my_module", Context);
// 前端解析
Lexer lexer(source);
Parser parser(lexer);
auto ast = parser.parseExpression();
if (!ast) {
return 1;
}
// 生成IR
Function* mainFunc = createMainFunction(ast.get());
if (!mainFunc) {
return 1;
}
// 优化IR
optimizeModule();
// 生成目标文件
if (!generateObjectFile(argv[2])) {
return 1;
}
// 打印生成的IR(可选)
Mod->print(outs(), nullptr);
return 0;
}
编译与测试
将编译器代码编译完成后,执行以下命令生成目标文件,再链接为可执行程序:
# 编译自定义编译器 g++ -std=c++14 main.cpp `llvm-config --cxxflags --ldflags --libs` -o my_compiler # 使用编译器生成目标文件 ./my_compiler input.txt output.o # 链接目标文件生成可执行程序 g++ output.o -o calc # 运行程序 ./calc # 输出结果应为0,对应1+2-3的计算结果
实际开发中可以根据需求扩展语法支持,比如增加变量定义、函数调用、控制流语句等,只需要对应扩展AST节点类型和IR生成逻辑即可。LLVM提供了丰富的API支持各类语言特性的实现,开发者可以参考LLVM官方文档了解更详细的接口用法。
C++LLVM_IRAOT_compiler编译器构建修改时间:2026-07-13 06:12:42