必须显式注册自定义pass:在代码中使用static registerpass x("my-matmul-opt", "optimize matrix multiply loops", false, false)宏,确保pass id字符串与命令行参数名完全一致,否则opt将报“unknown pass”。

怎么注册并让 LLVM 认出你的自定义 Pass
LLVM 不会自动发现你写的优化 Pass,必须显式注册——否则 opt -load ./MyPass.so -my-matmul-opt 会报错 Unknown pass: my-matmul-opt。关键不是编译成 .so 就完事,得在代码里调用 registerPass 或(更推荐)用 RegisterPass<mymatmulopt></mymatmulopt> 宏,并确保 Pass ID 的字符串和命令行参数名一致。
常见错误是漏掉 static RegisterPass<mymatmulopt> X("my-matmul-opt", "Optimize matrix multiply loops", false, false);</mymatmulopt> 这一行;或者把构造函数里传的 ID 字符串写成 "matmul-opt",但命令行却用 -my-matmul-opt,导致匹配失败。
- Pass 类必须继承
FunctionPass(对整个函数做变换)或LoopPass(更合适,因为矩阵乘通常落在循环嵌套里) - 如果用
LoopPass,必须重写runOnLoop,并在getAnalysisUsage中声明依赖LoopInfoWrapperPass和ScalarEvolutionWrapperPass - LLVM 14+ 默认启用 New PM(Pass Manager),若想兼容旧版
opt命令,注册时加INITIALIZE_PASS_BEGIN(...)宏链;否则只支持opt -passes="my-matmul-opt"形式
怎么识别合法的矩阵乘循环模式
盲目优化任意三重循环会引入错误,必须先验证是否符合典型 GEMM(C[i][j] += A[i][k] * B[k][j])结构。LLVM IR 层没有“二维数组”概念,所有访问都降为一维地址计算,所以得靠 GetElementPtrInst 的步长、索引组合和循环变量关系来反推。
核心判断点:外层循环变量是否同时出现在 A 和 C 的第一个维度、中层是否只出现在 A 的第二维和 B 的第一维、内层是否只出现在 B 的第二维和 C 的第二维——这需要结合 ScalarEvolution 分析各 PHI 和 IndVar 的符号表达式。
- 别只看循环层数:有些编译器已将矩阵乘展开或向量化,此时原始循环结构已被破坏,
LoopPass拿不到三层嵌套 - 检查
LoadInst地址是否都来自同一 base pointer(如%A,%B,%C),且 offset 表达式含且仅含两个循环变量(如i * M + k) - 用
Loop::getHeader()->getTerminator()确认是br i1 %cond, label %loop, label %exit结构,排除 do-while 或 goto loop
怎么安全地做循环分块(Loop Tiling)并生成向量化代码
直接改 IR 做分块极易出错:比如没更新 PHI 节点的入边、漏掉对 BranchInst 的条件重算、或让 StoreInst 写到越界地址。LLVM 提供 LoopVectorizePass 和 LoopInterchangePass 是现成轮子,但它们不针对矩阵乘定制,常因依赖分析保守而跳过。更可控的做法是:先用 LoopVectorizePass 做基础向量化,再在其后插入你的 MyMatMulOpt,专做分块 + 手动插入 shuffle/fmadd 风格的融合乘加指令。
- 分块大小不能硬编码:用
TargetTransformInfo::getRegisterBitWidth(true)获取目标平台向量寄存器宽度(如 AVX2 是 256),再除以元素类型字节(float=4 → 8 元素/向量),反推最优 tile size - 插入新指令必须用
IRBuilder绑定到正确InsertPoint:比如在内层循环体开头插load,在结尾插store,中间用fmul+fadd替代load→fmul→load→fadd→store - 务必调用
LoopInfo::replaceLoopWithNewLoop或手动更新Loop的Blocks成员,否则后续 Pass 可能因 LoopInfo 过期而崩溃
为什么你的 Pass 在 Release 模式下没生效
最常被忽略的是优化层级冲突:如果你的 Pass 插在 -O2 流程里,但 LLVM 已在更早阶段(如 EarlyCSEPass)把矩阵乘的冗余 load 消掉了,或者 SCEVPass 把循环变量关系判定为不可分析,你的识别逻辑就会返回 false,直接跳过优化。这不是你的 Pass 写错了,而是它没拿到“干净”的输入 IR。
- 调试时用
opt -S -debug-pass=Structure your_pass.bc 2>&1 | grep -A5 -B5 matmul查看 Pass 实际运行顺序和触发位置 - 确保测试用例禁用其他优化干扰:用
clang -O0 -Xclang -disable-llvm-passes -emit-llvm生成原始 IR,再用opt -load ./MyPass.so -my-matmul-opt单独跑你的 Pass - 矩阵尺寸太小(如 4x4)会被标量优化吞掉;太大(如 1024x1024)可能触发内存访问乱序,需配合
prefetch插入,但PrefetchInst在某些 backend(如 AArch64)不生成实际指令,得查TargetLowering::isPrefetchInstr
矩阵运算优化真正难的不是写分块逻辑,而是让 Pass 在不同编译阶段、不同 target、不同矩阵布局(row-major vs column-major)下都能稳定识别并安全改写——边界条件比主干逻辑多三倍。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











