loweroperation 是 targetlowering 类的虚函数,必须重写,负责将 ir 抽象操作(如 isd::sdiv)映射为目标平台 sdnode(如 riscv::add 或 cpu0isd::divrem);跳过会导致通用 lowering 膨胀指令、自定义指令不识别、类型合法化失效、setoperationaction 策略失活。

直接说结论:LowerOperation 是 TargetLowering 类里的虚函数,必须重写,它负责把 LLVM IR 中的抽象操作(比如 ISD::ADD、ISD::SDIV)映射成目标平台能理解的 SDNode 节点(如 RISCV::ADD 或自定义的 Cpu0ISD::DivRem),不重写就走默认兜底逻辑,大概率生成错误或低效代码。
为什么 LowerOperation 不能跳过
LLVM IR 是平台无关的,而硬件指令是具体的。SelectionDAGBuilder 把 IR 指令转成 SDNode 后,会调用 LowerOperation 对每个节点做“合法化”和“定制化”处理。跳过它,意味着:
- 所有除法、浮点比较、向量操作等都走通用 lowering,可能膨胀成几十条指令
- 自定义指令(如 RISC-V 的 clz)根本不会被识别
- 类型不匹配时(比如 i16 除法在只支持 i32 运算的 CPU 上)不会触发 expand 或 promote 等合法化动作
- setOperationAction 设置的 Expand、Legalize 等策略不会生效
LowerOperation 常见实现模式
典型写法是 switch (Op.getOpcode()),对不同 opcode 分支处理。关键点不是“写完就行”,而是要匹配目标硬件的真实能力:
- 对原生支持的操作(如 RISC-V 的
add),直接构造目标指令节点:DAG.getNode(RISCV::ADD, ...) - 对不支持的操作(如 64-bit 除法在 32-bit CPU 上),返回
SDValue()让框架自动 expand 成库调用或软实现 - 对需拆解的操作(如
ISD::SDIVREM),要检查N->hasAnyUseOfValue(0)和N->hasAnyUseOfValue(1),只生成实际需要的MFLO/MFHI指令,避免冗余读寄存器 - 对带副作用的 intrinsic(如
llvm.riscv.clz),需先判断是否已由LowerIntrinsicCall处理,避免重复 lowering
容易踩的坑
这个函数看着简单,但调试起来最头疼的地方往往藏在细节里:
-
SDLoc(Op)必须传进去,否则生成的汇编行号错乱,gdb 调试时定位不到源码位置 - 返回的
SDNode *如果类型(MVT)和原节点不一致,后续 pass 会 crash,比如把MVT::i32返回成MVT::i64 - 没处理
DCI.isBeforeLegalizeOps()就直接访问 operand,可能在 legalize 前拿到非法值(比如未展开的 vector 类型) - 忘记调用
DAG.ReplaceAllUsesOfValueWith替换旧节点,导致 DAG 中残留死节点,影响寄存器分配
真正难的不是写对一个 ADD,而是让整套 lowering 在所有优化级别、所有类型组合、所有调用上下文中都稳定输出正确且紧凑的指令——这要求你对硬件手册、TableGen 描述、LLVM 的 type legalization 规则三者之间的咬合点非常清楚。











