machinefunctionpass 是作用于 machinefunction(指令选择后、寄存器分配前的机器指令序列)的后端优化 pass,继承自 functionpass 但操作对象是 machineinstr 和虚拟寄存器;而 functionpass 仅作用于 llvm ir 层,无法触及目标相关指令与后端优化。

MachineFunctionPass 是什么,和 FunctionPass 有什么区别
它不是在 LLVM IR 层操作的 FunctionPass,而是在指令选择(Instruction Selection)之后、寄存器分配之前,对 MachineFunction(即已生成目标相关指令的函数表示)进行遍历和修改的 pass。此时 IR 已被翻译成 MachineInstr 序列,但还没做寄存器分配,变量还是虚拟寄存器(VirtualRegister),所以你能安全地插入/删除/重写机器指令,但不能假设物理寄存器已就绪。
关键区别在于:如果你要插桩、重排指令顺序、做后端特定优化(比如 ARM 的 predication 消除、RISC-V 的 load-store 合并),或者像 OLLVM 那样做控制流混淆(后端级),就必须用 MachineFunctionPass;用 FunctionPass 是改不到这些的——它看到的还是 %add = add i32 %0, %1 这种 IR,根本不知道最终会变成 add x0, x1, x2 还是 addw r0, r1, r2。
注册一个 MachineFunctionPass 的最小必要步骤
不依赖 TableGen、不碰 CMakeLists.txt 全局配置,只改你自己的文件,就能让 llc 或 opt 认出它:
- 继承
MachineFunctionPass,实现runOnMachineFunction(MachineFunction &MF) - 定义静态
ID成员(必须是char类型,不能是int或const char*) - 用
INITIALIZE_PASS宏注册,参数顺序不能错:INITIALIZE_PASS(YourPassClass, "pass-name", "Pass description", false, false) - 在对应 Target 的头文件(如
ARM.h)里声明工厂函数:FunctionPass *createYourPass(); - 在源文件末尾提供该工厂函数定义,返回
new YourPassClass()
漏掉任意一条,llc -load=yourpass.so -your-pass-name 就会报 Unknown pass: your-pass-name 或直接 segfault。
runOnMachineFunction 里怎么安全访问和修改指令
别直接遍历 MF.instr_begin() 然后 erase() ——这会导致迭代器失效。正确做法是先收集要改的 MachineInstr*,再批量处理:
- 遍历每个
MachineBasicBlock:for (auto &MBB : MF) - 遍历块内指令:
for (auto &MI : MBB),注意MI是MachineInstr&,不是指针 - 用
MI.getOpcode() == ARM::ADDrr(或TargetOpcode::COPY等)判断指令类型,别用字符串比较 - 插入新指令用
MBB.insert(MI, newMI),其中newMI由BuildMI(MBB, MI, DebugLoc(), TII->get(ARM::SUBrr))构造 - 删除用
MI.eraseFromParent(),不是MBB.erase(MI)
常见坑:BuildMI 第二个参数必须是 MachineInstr*(定位插入点),传 MI 本身是错的——得传 &MI 或 MI.getIterator().getBase();另外,所有虚拟寄存器(Reg)必须通过 MRI.createVirtualRegister(&TRI) 创建,不能硬编码 ARM::R0 这类物理寄存器。
怎么编译、加载、验证你的 MachineFunctionPass
它不能像 IR pass 那样用 opt -load=xxx.so 直接跑,因为 opt 只处理 IR 层;你必须走 llc 流水线:
- 编译成 so:
clang++ -fPIC -shared -I$LLVM_BUILD/include -I$LLVM_SRC/include ... -o YourPass.so,链接-lLLVMCodeGen -lLLVMCore - 触发方式:
llc -load=YourPass.so -your-pass-name input.ll -o /dev/null -debug-only=your-pass-name - 加
-print-after-all可以看到它执行前后MachineInstr的变化,输出在 stderr - 如果 pass 没生效,先检查
llc --help | grep your-pass-name是否列出——没列说明注册失败;再确认DEBUG_TYPE和-debug-only=后缀是否完全一致(大小写敏感)
最容易被忽略的是:MachineFunctionPass 默认不会被 llc 自动启用,哪怕你注册了,也必须显式用 -your-pass-name 参数触发;而且它只在 -O0 或 -O1 下稳定工作,-O2 可能因流水线跳过某些阶段导致你的 pass 被绕过。











