LLVM后端开发是“边搭边学”,必须提前确认三件事:能否读懂llc生成的汇编、是否写过FunctionPass、是否本地成功构建过含llvm/lib/Target/的完整LLVM,缺一即卡在无输出阶段。

直接说结论:LLVM 后端开发不是“学完再动手”,而是“边搭边学”,但必须提前确认三件事——你是否已能读懂 llc 生成的汇编、是否写过至少一个 FunctionPass、是否在本地成功构建过带 llvm/lib/Target/ 的完整 LLVM。缺任何一项,后面都会卡在“连输出都看不到”的阶段。
先搞懂 LLVM 后端到底要你做什么
后端不是写汇编,而是把 LLVM IR 映射成目标平台的指令序列,并保证语义等价、调用约定合规、寄存器分配合理。它不处理语法、类型检查或高级优化,只负责“最后一公里”:IR → SelectionDAG → MachineInstr → MCInst → .s 或 .o。
- 常见错误现象:
llc -march=mytarget报错Target not registered,说明你的 Target 没被 CMake 扫到,或LLVM_TARGETS_TO_BUILD没包含它 - 关键使用场景:为 RISC-V 扩展自定义指令、给国产 DSP 芯片生成代码、为 FPGA 控制器生成软核指令流
- 性能影响点:指令选择(
SelectionDAGISel)和寄存器分配(RegisterAllocator)是耗时大头,别一上来就改AsmPrinter
必须提前掌握的四个硬知识
这些不是“建议了解”,而是你敲下第一个 TableGen 命令前就得闭环的问题:
- 目标平台 ABI 和调用约定:比如 ARM64 的
x0-x7是参数寄存器,x29是帧指针;RISC-V 的a0-a7是参数,s0-s11是 callee-save。不熟这个,getTargetLowering()会写错 - LLVM IR 的 SSA 特性与基本块结构:后端不解析源码,只看
%0 = add i32 %1, %2这类指令。如果你不能从.ll文件里快速定位 PHI 节点和支配边界,MachineBasicBlock构建会出逻辑错误 -
TableGen的核心作用:它不是模板引擎,而是把.td文件里的def ADD : Instruction编译成 C++ 类声明和匹配逻辑。不会写InstrInfo.td,你就没法让SelectionDAG认出你的加法指令 - CMake 构建链路:LLVM 后端模块必须被
add_llvm_library(LLVMMyTargetCodeGen)注册,且LLVM_TARGETS_TO_BUILD="MyTarget"必须传给顶层cmake。漏掉这步,lib/Target/MyTarget/目录根本不会参与编译
从哪开始写第一行后端代码
别碰 TargetMachine 或 AsmPrinter,从最薄的可运行路径切入:
- 复制
llvm/lib/Target/ARM/下的最小骨架(ARMTargetMachine.cpp,ARMSubtarget.h,ARMInstrInfo.td),重命名并删减 90% 内容,只保留一条指令(如MOVrr)和一个寄存器类(如GPR) - 确保
llc -march=mytarget input.ll -o out.s能跑通,哪怕输出是空文件或报错unhandled instruction—— 这说明 Target 已注册,只是没匹配上 - 在
MyTargetInstrInfo.td里加def MOVrr : MyInst;,然后重新构建,再试llc - 容易踩的坑:
MyTarget.td必须在TargetInfo/CMakeLists.txt里被tablegen调用;MyTargetTargetMachine.cpp的构造函数里必须调用initAsmInfo(),否则-asm-verbose会崩
真正卡住大多数人的地方,从来不是指令编码或调度算法,而是构建系统没把你的 Target 编进 libLLVM.so,或者 llc 根本没加载你的 LLVMInitializeMyTargetTargetInfo() 函数。先让 nm libLLVM.so | grep MyTarget 看符号是否存在,再谈 IR 到机器码的映射细节。

















