targetmachine 类必须实现,否则 llc 无法识别目标架构;它负责注册子系统、控制 jit/mc 支持及调度/分配策略,缺之则运行时报 invalid target 错误。

TargetMachine 类必须实现,否则 llc 无法识别目标
LLVM 后端启动时,llc 依赖 TargetMachine 的全局注册来发现可用架构。没实现它,llc -march=yourtarget 会直接报错 error: invalid target 'yourtarget'。
这个类不只是“注册入口”,它还承担三件事:
- 构造并返回
TargetLowering、TargetInstrInfo、TargetRegisterInfo等子系统实例 - 决定是否启用 JIT、是否支持 MC layer(影响
llc -filetype=obj是否可用) - 控制默认的指令调度器(如
TargetSchedModel)和寄存器分配策略
常见错误是只注册了 Target 全局变量但没提供 TargetMachine 子类,导致编译通过但运行时报错。
TargetLowering 是 IR 到 SelectionDAG 转换的关键桥梁
TargetLowering 决定了你的架构如何理解 LLVM IR 中的基本操作——比如 add、load、call 是映射成一条原生指令,还是拆成多条,或者根本需要自定义 lowering 逻辑。
你必须重写 LowerOperation 来处理 IR 指令到 SDNode 的转换,尤其是:
-
ISD::ADD/ISD::MUL等算术节点:是否支持 immediate operand?是否要拆成ADDri+ADDrr? -
ISD::LOAD/ISD::STORE:地址模式支持哪些?是否允许 post-increment?对齐要求是否严格? -
ISD::CALL:调用约定怎么描述?参数怎么传?返回值放哪?栈帧怎么管理?
漏掉任意一个常用 ISD::* 节点的 lowering,llc 在 SelectionDAGISel::Select 阶段就会 abort 并提示 Do not know how to lower this operator!。
TargetInstrInfo 和 TargetRegisterInfo 是指令调度与寄存器分配的前提
没有 TargetInstrInfo,你就没法告诉 LLVM “我的 ADD 指令延迟是 1,有 2 个读操作数、1 个写操作数”;没有 TargetRegisterInfo,寄存器分配器连 “R0 是保留寄存器” 或 “R1–R10 是 caller-saved” 都不知道。
这两个类不是可选的——只要你想生成可执行代码(哪怕只是 .s),就必须提供基础实现:
-
TargetInstrInfo至少要实现getRegClass、getOperandConstraint、isTriviallyReMaterializable -
TargetRegisterInfo必须提供寄存器枚举(enum { R0, R1, ... })、寄存器类定义(GR32)、以及getCalleeSavedRegs
典型坑:寄存器编号从 0 开始但忘了把 R0 设为 Reserved,结果被分配器乱用,生成非法指令。
TableGen 描述文件比 C++ 代码更早生效,且容错极低
LLVM 构建时,.td 文件(如 YourTarget.td、YourTargetInstrInfo.td)会被 tblgen 编译成 C++ 头文件。这些头文件是 TargetInstrInfo、TargetRegisterInfo 等类的底层数据源。
一旦 .td 文件语法出错或语义矛盾(比如定义了两个同名指令,或寄存器类包含不存在的寄存器),tblgen 直接失败,整个构建中断,且错误信息极其简陋,常只显示 error: redefinition of 'XXX'。
实操建议:
- 先用最小
YourTarget.td:只定义Target、一个空Instruction、一组寄存器,确保tblgen能过 - 所有寄存器名、指令名、寄存器类名,在
.td里定义一次,C++ 里只引用,不要手动重复枚举 - 避免在
.td中使用复杂let嵌套,调试困难;优先用def+multiclass组织
真正卡住新人的往往不是 C++ 逻辑,而是 tblgen 这一层无声崩溃——它不报行号,也不提示上下文,只留一个 exit code 1。











