LLVM 开发指南 / 第 14 章:后端目标开发
第 14 章:后端目标开发
“为一个新架构添加 LLVM 后端,就是在用代码描述硬件。”
14.1 后端开发概述
14.1.1 何时需要开发后端?
| 场景 | 说明 |
|---|---|
| 新处理器架构 | 为自研 CPU 添加编译器支持 |
| 自定义加速器 | FPGA、DSP、AI 加速器 |
| 安全处理器 | 专用 ISA(如 CHERI) |
| 学术研究 | 探索新指令集设计 |
14.1.2 后端组件
lib/Target/MyTarget/
├── CMakeLists.txt # 构建配置
├── MyTarget.td # 顶层 TableGen 文件
├── MyTargetInstrInfo.td # 指令定义
├── MyTargetRegisterInfo.td # 寄存器定义
├── MyTarget.td # 子目标定义
├── MyTargetISelLowering.h/cpp # 选择低层
├── MyTargetInstrInfo.h/cpp # 指令信息
├── MyTargetRegisterInfo.h/cpp # 寄存器信息
├── MyTargetFrameLowering.h/cpp # 帧低层
├── MyTargetSubtarget.h/cpp # 子目标特性
├── MyTargetMachine.h/cpp # Target Machine
├── MyTargetAsmPrinter.h/cpp # 汇编输出
├── MyTargetMCInstLower.h/cpp # MCInst 转换
└── MyTargetISelDAGToDAG.cpp # 指令选择
14.2 TableGen 语言
TableGen 是 LLVM 的领域特定语言,用于描述处理器特性。
14.2.1 基本语法
// 定义类
class Register<string name, int encoding> {
string Name = name;
int Encoding = encoding;
list<Register> Aliases = [];
list<Register> SubRegs = [];
}
// 继承并实例化
class GPR<int enc> : Register<"", enc> {
let RegClass = GPR;
}
// 实例化寄存器
def R0 : GPR<0>;
def R1 : GPR<1>;
def R2 : GPR<2>;
def R3 : GPR<3>;
def SP : GPR<13> { let Name = "sp"; }
def LR : GPR<14> { let Name = "lr"; }
def PC : GPR<15> { let Name = "pc"; }
14.2.2 数据类型
| 类型 | 说明 | 示例 |
|---|---|---|
bit | 1 位 | bit isTerminator = 1; |
int | 整数 | int Size = 4; |
string | 字符串 | string Name = "add"; |
bits<n> | n 位 | bits<8> Opcode = 0x01; |
list<T> | 列表 | list<Register> Regs; |
code | 代码块 | code PrintMethod = [{ ... }]; |
14.2.3 常用 TableGen 记录
// 定义寄存器类
def GPR : RegisterClass<"MyTarget", [i32], 32,
(add R0, R1, R2, R3, R4, R5, R6, R7)>;
def FPR : RegisterClass<"MyTarget", [f32], 32,
(add F0, F1, F2, F3, F4, F5, F6, F7)>;
// 定义指令格式
class MyInst<dag outs, dag ins, string asm, list<dag> pattern>
: Instruction {
let OutOperandList = outs;
let InOperandList = ins;
let AsmString = asm;
let Pattern = pattern;
}
// 定义具体指令
def ADDrr : MyInst<
(outs GPR:$dst), // 输出操作数
(ins GPR:$src1, GPR:$src2), // 输入操作数
"add $dst, $src1, $src2", // 汇编格式
[(set GPR:$dst, (add GPR:$src1, GPR:$src2))] // 匹配模式
>;
def ADDri : MyInst<
(outs GPR:$dst),
(ins GPR:$src1, i32imm:$imm),
"add $dst, $src1, $imm",
[(set GPR:$dst, (add GPR:$src1, imm:$imm))]
>;
// 加载指令
def LDri : MyInst<
(outs GPR:$dst),
(ins GPR:$base, i32imm:$off),
"ld $dst, [$base, $off]",
[(set GPR:$dst, (load (add GPR:$base, imm:$off)))]
>;
// 存储指令
def STri : MyInst<
(outs),
(ins GPR:$src, GPR:$base, i32imm:$off),
"st $src, [$base, $off]",
[(store GPR:$src, (add GPR:$base, imm:$off))]
>;
// 分支指令
def BR : MyInst<
(outs),
(ins brtarget:$target),
"b $target",
[(br bb:$target)]
> {
let isBranch = 1;
let isTerminator = 1;
let isBarrier = 1;
}
def BREQ : MyInst<
(outs),
(ins GPR:$src1, GPR:$src2, brtarget:$target),
"beq $src1, $src2, $target",
[]
> {
let isBranch = 1;
let isTerminator = 1;
let isConditional = 1;
}
14.3 寄存器定义
14.3.1 寄存器文件描述
// MyTargetRegisterInfo.td
// 物理寄存器
def R0 : MyReg<0, "r0">, DwarfRegNum<[0]>;
def R1 : MyReg<1, "r1">, DwarfRegNum<[1]>;
def R2 : MyReg<2, "r2">, DwarfRegNum<[2]>;
def R3 : MyReg<3, "r3">, DwarfRegNum<[3]>;
def R4 : MyReg<4, "r4">, DwarfRegNum<[4]>;
def R5 : MyReg<5, "r5">, DwarfRegNum<[5]>;
def R6 : MyReg<6, "r6">, DwarfRegNum<[6]>;
def R7 : MyReg<7, "r7">, DwarfRegNum<[7]>;
def SP : MyReg<13, "sp">, DwarfRegNum<[13]>;
def LR : MyReg<14, "lr">, DwarfRegNum<[14]>;
// 寄存器类 — 定义寄存器分组
def GPR : RegisterClass<"MyTarget", [i32], 32, (add
R0, R1, R2, R3, R4, R5, R6, R7, SP, LR
)>;
// 寄存器元组(用于指令需要多个连续寄存器时)
def R0R1 : RegisterTup<2, GPR, [R0, R1]>;
def R2R3 : RegisterTup<2, GPR, [R2, R3]>;
// 寄存器序列
def GPRPair : RegisterClass<"MyTarget", [i64], 64, (add R0R1, R2R3)>;
14.4 指令选择
14.4.1 TargetLowering
// MyTargetISelLowering.cpp
MyTargetTargetLowering::MyTargetTargetLowering(
const MyTargetTM &TM, const MyTargetSubtarget &STI)
: TargetLowering(TM) {
// 设置寄存器类型
addRegisterClass(MVT::i32, &MyTarget::GPRRegClass);
addRegisterClass(MVT::f32, &MyTarget::FPRRegClass);
// 设置操作合法化策略
// 对于 64 位操作,扩展为两个 32 位操作
setOperationAction(ISD::ADD, MVT::i64, Expand);
setOperationAction(ISD::MUL, MVT::i64, Expand);
// 对于 select,自定义 lowering
setOperationAction(ISD::SELECT, MVT::i32, Custom);
// 对于位操作,设为合法
setOperationAction(ISD::AND, MVT::i32, Legal);
setOperationAction(ISD::OR, MVT::i32, Legal);
setOperationAction(ISD::XOR, MVT::i32, Legal);
}
SDValue MyTargetTargetLowering::LowerOperation(
SDValue Op, SelectionDAG &DAG) const {
switch (Op.getOpcode()) {
case ISD::SELECT:
return LowerSELECT(Op, DAG);
default:
llvm_unreachable("未实现的操作");
}
}
14.4.2 ISelDAGToDAG
// MyTargetISelDAGToDAG.cpp
void MyTargetDAGToDAGISel::Select(SDValue &Node) {
SDLoc DL(Node);
switch (Node->getOpcode()) {
case ISD::LOAD: {
// 选择加载指令变体
// 检查偏移是否适合 LDri 指令
auto *LoadNode = cast<LoadSDNode>(Node);
if (LoadNode->getAddressingMode() == ISD::UNINDEXED) {
SDValue Base = LoadNode->getBasePtr();
SDValue Offset = LoadNode->getOffset();
if (auto *CI = dyn_cast<ConstantSDNode>(Offset)) {
if (isInt<12>(CI->getSExtValue())) {
// 使用立即数偏移的加载
Node = CurDAG->getMachineNode(
MyTarget::LDri, DL, MVT::i32,
Base, Offset);
return;
}
}
}
break;
}
}
// 默认处理
SelectCode(Node);
}
14.5 使用 TableGen 生成代码
# 查看 TableGen 生成的文件
# 在 LLVM 构建目录下:
# build/lib/Target/MyTarget/
# 生成所有包含文件
llvm-tblgen -gen-instr-info MyTarget.td -I ../../include
llvm-tblgen -gen-register-info MyTarget.td -I ../../include
llvm-tblgen -gen-asm-writer MyTarget.td -I ../../include
llvm-tblgen -gen-disassembler MyTarget.td -I ../../include
llvm-tblgen -gen-emitter MyTarget.td -I ../../include
llvm-tblgen -gen-global-isel MyTarget.td -I ../../include
llvm-tblgen -gen-searchable-tables MyTarget.td -I ../../include
# 生成的文件:
# MyTargetGenInstrInfo.inc — 指令信息
# MyTargetGenRegisterInfo.inc — 寄存器信息
# MyTargetGenAsmWriter.inc — 汇编输出
# MyTargetGenDisassembler.inc — 反汇编
# MyTargetGenMCCodeEmitter.inc — MC 编码器
14.6 后端测试
# 使用 llc 测试后端
llc -mtriple=mytarget-unknown-elf test.ll -o test.s
# 查看指令选择结果
llc -mtriple=mytarget-unknown-elf -print-after-isel test.ll -o /dev/null 2>&1
# 查看寄存器分配
llc -mtriple=mytarget-unknown-elf -print-after-regalloc test.ll -o /dev/null 2>&1
# 使用 MIR 测试
llc -mtriple=mytarget-unknown-elf -stop-after=instruction-select test.ll -o test.mir
# LLVM 后端测试套件
# test/CodeGen/MyTarget/
# - 每个 .ll 文件是一个测试
# - 使用 FileCheck 验证输出
14.6.1 使用 FileCheck 测试
; test/CodeGen/MyTarget/add.ll
; RUN: llc -mtriple=mytarget-unknown-elf < %s | FileCheck %s
; CHECK-LABEL: test_add:
; CHECK: add r0, r0, r1
; CHECK: ret
define i32 @test_add(i32 %a, i32 %b) {
%r = add i32 %a, %b
ret i32 %r
}
14.7 GlobalISel(新指令选择框架)
LLVM 正在从 SelectionDAG 迁移到 GlobalISel:
传统: IR → SelectionDAG → MachineInstr
新: IR → GMIR → Legalize → Select → MachineInstr
(GlobalISel 通用机器 IR)
| 特性 | SelectionDAG | GlobalISel |
|---|---|---|
| 作用范围 | 单个基本块 | 整个函数 |
| 编译速度 | 较慢 | 更快 |
| 成熟度 | 非常成熟 | 仍在发展中 |
| 使用目标 | AArch64, x86 等 | AArch64 (默认) |
14.8 本章小结
| 组件 | 说明 |
|---|---|
| TableGen | 声明式语言,描述处理器特性 |
| 寄存器定义 | RegisterClass, RegisterTup |
| 指令定义 | Instruction, Pattern |
| TargetMachine | 后端入口点 |
| TargetLowering | 操作合法化 |
| ISelDAGToDAG | 指令选择 |
| AsmPrinter | 汇编输出 |
扩展阅读
- Writing an LLVM Backend — 后端开发教程
- TableGen Programmer’s Reference — TableGen 参考
- GlobalISel — 新指令选择框架
- RISCV backend — RISC-V 后端参考
下一章: 第 15 章:Sanitizers — 学习 LLVM Sanitizers 的实现原理。