强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 数据类型

类型说明示例
bit1 位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)
特性SelectionDAGGlobalISel
作用范围单个基本块整个函数
编译速度较慢更快
成熟度非常成熟仍在发展中
使用目标AArch64, x86 等AArch64 (默认)

14.8 本章小结

组件说明
TableGen声明式语言,描述处理器特性
寄存器定义RegisterClass, RegisterTup
指令定义Instruction, Pattern
TargetMachine后端入口点
TargetLowering操作合法化
ISelDAGToDAG指令选择
AsmPrinter汇编输出

扩展阅读

  1. Writing an LLVM Backend — 后端开发教程
  2. TableGen Programmer’s Reference — TableGen 参考
  3. GlobalISel — 新指令选择框架
  4. RISCV backend — RISC-V 后端参考

下一章: 第 15 章:Sanitizers — 学习 LLVM Sanitizers 的实现原理。