匹配规则主要写在xxxinstrinfo.td文件中,通过pat/patfrag定义声明式模式;xxxiseldagtodag.cpp中的select函数仅用于tablegen无法处理的极少数命令式场景,如动态检查flags或类型切换。

匹配规则写在哪:XXXInstrInfo.td 和 XXXISelDAGToDAG.cpp
LLVM 后端的指令匹配不是靠手写 if-else 遍历节点,而是分两层:TableGen 自动生成的模式匹配表(声明式),加上少量 C++ 手动补丁(命令式)。核心入口是 XXXInstrInfo.td 文件里的 Pat 或 PatFrag 定义;而 XXXISelDAGToDAG.cpp 中的 Select 函数只用于绕过 TableGen 限制的极少数场景,比如需要动态判断寄存器类、或依赖 DAG 节点属性做分支。
常见错误是把所有逻辑塞进 Select,结果调试时发现模式根本没触发——因为 TableGen 生成的 SelectCode 在 Select 之前就跑完了,且不走虚函数调用路径。
-
XXXInstrInfo.td是主战场,所有标准 IR 操作(如ISD::ADD、ISD::LOAD)都应优先在这里配Pat -
XXXISelDAGToDAG.cpp只处理 TableGen 做不了的事:比如检查SDNode::getFlags()是否含ISD::FlagVolatile,或根据 operand 的getValueType()切换指令变体 - 别在
Select里调用DAG.getNode()构造新节点再递归 Select——这会破坏 DAG 合法化顺序,容易引发 infinite loop
Pat 规则怎么写才不被忽略:类型、约束、顺序三者缺一不可
一个 Pat 不生效,90% 是因为类型不匹配或约束未满足。TableGen 匹配时先校验操作数类型(MVT::i32 vs MVT::i64),再检查 Predicates(如 HasStdExtM),最后才比对结构。写错任意一项,整条规则就静默跳过。
例如 RISC-V 的乘法指令支持,不能只写:
def : Pat;
必须显式带上类型约束和扩展依赖:
def : Pat, Requires;
-
Requires必须存在,否则即使目标支持该扩展,规则也不会启用 - 操作数类型必须和 IR 实际生成的一致:
mul i32对应MVT::i32,但若 lowering 阶段已将 i32 改为 i64,则这条规则永远不触发 - 避免用
imm直接匹配立即数——RISC-V 的addi要求-2048 ,得用 <code>ImmLeaf<...></...>+ 自定义 C++ predicate 校验范围
为什么 Pattern 匹配失败却没报错
LLVM 默认不会告诉你哪条 Pat 被跳过了。它只在完全找不到匹配时 fallback 到 generic expansion(比如把 mul 拆成 shift+add 序列),此时你看到的是“功能正常但性能差”,而不是编译错误。
调试手段很直接:
- 加
-debug-only=isel运行llc,看日志里有没有Trying to select: ...和后续的Match failed行 - 在
XXXISelDAGToDAG.cpp的Select开头打 log,确认是否进入该函数——如果没进,说明 TableGen 已经 match 成功;如果进了但没返回,说明你的手动逻辑卡住了 - 用
llc -view-isel-dags生成 .dot 图,人工核对 DAG 节点类型和 operand 结构是否与Pat左侧一致
自定义指令的 Pat 必须关联 intrinsic
如果你新增的是硬件专属指令(比如 RISCV::AES_ENC),不能直接匹配 IR 的 call @llvm.riscv.aes.enc。必须先确保 Clang 端已声明对应 __builtin_riscv_aes_enc,且 LLVM IR 层已生成 call @llvm.riscv.aes.enc 调用;然后在 XXXInstrInfo.td 中写:
def : Pat;
这里的关键是左侧必须用 llvm.riscv.aes.enc 这个 intrinsic 名,而不是任意名字——TableGen 的 pattern matcher 只认 IR 中实际存在的函数名。
容易被忽略的一点:intrinsic 的参数类型必须和指令定义的 operand 类型严格对齐。比如 VR512 是向量寄存器类,但 intrinsic 声明里若写成 i512,pattern 就永远无法匹配,因为 IR 层生成的是 vector 而非标量 i512。











