windows上的LLVM pass瞎折腾记录
想了解下LLVM的代码风格和优化过程,就从写LLVM pass入手吧,做个踩坑记录…
文章最后有逐步骤的操作记录,Tips内记录关键点和思路
Tips:
- 首先不要在win上折腾llvm pass(本篇结束…),官方不支持得改代码,但是折腾一半实在是杠上了,所以还是硬着头皮搞了
- LLVM-PROJECT是各种工程的集合,写pass的话关注c语言前端clang,IR优化器opt这两个工程就好
- LLVM不同版本间差别很大,很多网络上的资料隔一个版本就不行了,所以这次一是能固定版本复现二是想搞清楚为啥就不行了三是尝试新方式
- 因为就是想用最新的llvm版本,在跟着飘云阁的哪个llvm文章的时候cmake出的工程没法编译,报有函数找不到符号,明显就是llvm的新版本加入了一些函数,实现在其他的lib里面,通过定位函数名-定位文件名-定位工程lib名的方式找齐然后修改cmake脚本成功构建
- 不想改pass还要重新编译llvm那个大工程所以怎么着都得用动态插件的形式才能实现,动态插件分两种一种是clang的插件形式一种是opt的插件形式,opt的插件形式网络上教程较多一般都是linux平台加载一个so然后用-pass=xxx调用 对clang生成的ll代码进行pass处理,遇到了各种问题比如win上的dll路径给他结果告诉我找不到所以根本连加载都没加载起来,具体是啥原因不清楚,但是本身先用clang编译出ll再进行opt指定的操作就不太方便所以最后还是选择了clang插件的方式
- 啥是clang插件?其实clang本身不像opt那样再命令行里面支持-load一个动态链接库的形式,所以所谓的clang插件是手动给clang的pass过程插入一个加载固定名称的dll并调用其固定函数名的导出函数的代码。如此这个固定名称的dll就成了我们的插件了,后续pass开发只编译这样的dll放到clang同目录下即可,实现了和llvm工程编译步骤分离的需要(还不是因为llvm工程编译太慢了)
- 如此生成的clang之后使用clang.exe test.cpp -o test.exe却并没有调用到populateModulePassManager函数中去,所以所谓的动态插件加载代码都没被执行,这个问题卡了最久然后突然想到看过llvm关于pass的介绍好像有介绍说啥新pass和旧pass来着,然后仔细查了下llvm的官方文档发现13.0.1版本以及是默认开启新的pass管理流程了,所以旧的pass管理函数populateModulePassManager就没走到。如果编译的时候想要编译旧的pass流程可以再cmake配置里面把LLVM_ENABLE_NEW_PASS_MANAGER关了,但是可以用更简单的方式:给clang调用的时候加一个-flegacy-pass-manager参数即可
- 第7点实际上以及搞定了只不过写pass的时候得按照旧的pass逻辑去写(其实大部分资料也都是旧pass),但是因为就是想折腾一下怎么不加上述参数也能实现llvm的动态插件机制(即新的pass逻辑),但是试了下好像不太行,想着关键是找到新pass流程中对应的populateModulePassManager函数,结果发现的疑似这样的函数PassBuilder::parseModulePass并没有被调用,这个类是初始化了的但是这个函数没被调用,不清楚啥情况。目前的思路是参考怎么再llvm内部用静态链接实现的addPass,然后就复用这样的流程然后把addpass里面的对象改为动态获取,但是看了一圈好像这样的addpass都是用的opt插件的形式(即:导出指定函数llvmGetPassPluginInfo,然后被opt -load加载),实际上不是llvm内的插件形式;还有种思路是跟踪-flegacy-pass-manager参数代码找到llvm中的新pass管理逻辑,具体实现方式还没找到,后续找到会更新(见第9点)
- 跟踪populateModulePassManager的引用网上找发现关键开关CGOpts.LegacyPassManager,因此确认新旧pass模式的函数区别为旧pass走EmitAssembly流程新pass走EmitAssemblyWithNewPassManager流程,后续新pass测试成功继续更新如何分离新pass和llvm 13.0增加clang动态插件
跟着搞一定能成功的具体步骤
- 下载13.0.1的llvm-project项目中的clang和llvm源码(llvm不同版本间变化较大建议不要改版本)
github页面下载:clang-13.0.1.src.tar.xz & llvm-13.0.1.src.tar.xz - 解压成文件夹后改名为llvm和clang(即去掉他们的版本号)并把他们统一放入llvm-project文件夹中;在llvm文件夹中建立一个build子文件夹;在llvm-project文件夹中新建一个MyCustom作为后续创建的工程目录,最后llvm-project中包含的文件夹(clang,llvm,MyCustom):不要变动目录结构不然后续cmake找不到源码
- cmake配置生成LLVM工程,其中几个需要修改:
LLVM_TARGET_ARCH填入X86只生成x86架构加速构建
LLVM_ENABLE_PROJECTS:填入clang生成clang opt等工具
搜索test和example关键字把看上去不必要构建的全取消了,加速构建
修改完成之后重新点配置然后生成,然后构建(release版本构建要快些) - 上述构建完之后参考这篇文章的前半部分把MyCustom工程构建完
注意1:他的cmake中的路径要改,两个头文件目录一个是llvm根目录下的include另一个是我们新建的build目录下的include
注意2:他的教程基于的是llvm 6.0,用新版需要去除PassSupport.h以及在CMakeLists.txt中的VTKLIBS列表内加上LLVMRemarks和LLVMBitstreamReader这两个lib,构建出和llvm版本一致的(release对release,debug对debug)输出文件 - 修改llvm-project\llvm\lib\Transforms\IPO\PassManagerBuilder.cpp文件的populateModulePassManager函数,在其中动态加载dll并获取dll的导出函数以及调用,完成后重新编译llvm(只做部分修改编译很快),修改完后的代码示意
.......
.......
#if _WINDOWS
#include "windows.h"
std::string GetStartPath(HMODULE hModule /* = NULL*/) {
char szTemp[260] = {0};
GetModuleFileNameA(hModule, szTemp, sizeof(szTemp) / sizeof(TCHAR));
strrchr(szTemp, '\\')[1] = 0;
return szTemp;
}
#endif // _WINDOWS
void PassManagerBuilder::populateModulePassManager(
legacy::PassManagerBase &MPM) {
#if _WINDOWS
using ClangAddCustomPass = void(__stdcall *)(legacy::PassManagerBase & MPM);
std::string strDllPath = GetStartPath(NULL) + "custompass.dll";
HMODULE hPassDll = ::LoadLibraryA(strDllPath.c_str());
ClangAddCustomPass pfn =
(ClangAddCustomPass)::GetProcAddress(hPassDll, "clangAddCustomPass");
if (pfn != NULL) {
pfn(MPM);
} else {
// not found custompass.dll or clangAddCustomPass
}
#endif // _WINDOWS
.......
.......
- 把第4步编译好的dll放入llvm-project/llvm/build/release/bin/目录下(release模式),并可放入你想测试的任何源码文件
- 命令:.\clang.exe -flegacy-pass-manager test.cpp -o test.exe即可发现pass成功执行
参考
1.Windows下的LLVM之把pass抽离到DLL中
2.对Windows下的LLVM之把pass抽离到DLL中的学习以及优化
3.LLVM Pass Windows折腾
4.llvm官方文档