Basic
这是一个 transformer pass, 效果类似于在每个函数的定义中插入如下语句:
printf("(llvm-tutor) Hello from: %s\n(llvm-tutor) number of arguments: %d\n",
FuncName, FuncNumArgs)
InjectFuncCall
pass 的注册与前 2 期类似, 不再解释。
类定义
struct InjectFuncCall : public llvm::PassInfoMixin<InjectFuncCall> {
llvm::PreservedAnalyses run(llvm::Module &M,
llvm::ModuleAnalysisManager &);
bool runOnModule(llvm::Module &M);
static bool isRequired() { return true; }
};
继承了 PassInfoMixin, 实现了 run 方法
runOnModule()
实现了主要功能: 将 Module 中每一函数体中添加上特定 printf 语句。返回是否对 Module 中的内容进行修改。
整体函数定义如下, 后面的小节对3个主要部分进行了拆解
bool InjectFuncCall::runOnModule(Module &M) {
bool InsertedAtLeastOnePrintf = false;
// 1: 在 module 中插入 printf 的声明, 效果类似 C 中的 int printf(char *, ...)
// ------------------------------------------------------------------------
auto &CTX = M.getContext();
PointerType *PrintfArgTy = PointerType::getUnqual(Type::getInt8Ty(CTX));
FunctionType *PrintfTy = FunctionType::get(
IntegerType::getInt32Ty(CTX),
PrintfArgTy,
/*IsVarArgs=*/true);
FunctionCallee Printf = M.getOrInsertFunction("printf", PrintfTy);
// Set attributes as per inferLibFuncAttributes in BuildLibCalls.cpp
Function *PrintfF = dyn_cast<Function>(Printf.getCallee());
PrintfF->setDoesNotThrow();
PrintfF->addParamAttr(0, Attribute::NoCapture);
PrintfF->addParamAttr(0, Attribute::ReadOnly);
// 2: 插入全局变量, 保存 format string: "(llvm-tutor) Hello from: %s\n(llvm-tutor) number of arguments: %d\n"
// ------------------------------------------------------------------------
llvm::Constant *PrintfFormatStr = llvm::ConstantDataArray::getString(
CTX, "(llvm-tutor) Hello from: %s\n(llvm-tutor) number of arguments: %d\n");
Constant *PrintfFormatStrVar =
M.getOrInsertGlobal("PrintfFormatStr", PrintfFormatStr->getType());
dyn_cast<GlobalVariable>(PrintfFormatStrVar)->setInitializer(PrintfFormatStr);
// 3: 遍历每个 function , 添加对 printf 的调用
// ----------------------------------------------------------------
for (auto &F : M) {
if (F.isDeclaration())
continue;
IRBuilder<> Builder(&*F.getEntryBlock().getFirstInsertionPt());
auto FuncName = Builder.CreateGlobalStringPtr(F.getName());
llvm::Value *FormatStrPtr =
Builder.CreatePointerCast(PrintfFormatStrVar, PrintfArgTy, "formatStr");
LLVM_DEBUG(dbgs() << " Injecting call to printf inside " << F.getName()
<< "\n");
Builder.CreateCall(
Printf, {FormatStrPtr, FuncName, Builder.getInt32(F.arg_size())});
InsertedAtLeastOnePrintf = true;
}
return InsertedAtLeastOnePrintf;
}
1: 在 module 中插入 printf 的声明, 效果类似 C 中的 int printf(char *, ...)
auto &CTX = M.getContext();
获取了 llvm 的全局 api 接口
PointerType *PrintfArgTy = PointerType::getUnqual(Type::getInt8Ty(CTX));
通过 CTX 获取了 8 位 int 类型。
PointerType::getUnqual 方法构造了一个指向 在默认地址空间 (地址 0) 下 指定类型的 object 的指针类型。
FunctionType::get 方法的第一个参数类型为 Type 指定了返回结果的类型, 第二个参数类型为ArrayRef<Type*> 指定了各个参数的类型, 第三个参数指定函数是否 可变参数。在这段代码中指定了函数 返回结果类型为 32 位 int, 函数参数类型为只含有一个 8 位 Int 指针类型的数组, 接收可变参数。
M.getOrInsertFunction 在 module 的 symbol table 中查找同名函数原型, 不存在则插入, 存在则对比原型, 使用旧原型替换新原型, 并返回 FunctionCallee
auto &CTX = M.getContext();
PointerType *PrintfArgTy = PointerType::getUnqual(Type::getInt8Ty(CTX));
FunctionType *PrintfTy = FunctionType::get(
IntegerType::getInt32Ty(CTX),
PrintfArgTy,
/*IsVarArgs=*/true);
FunctionCallee Printf = M.getOrInsertFunction("printf", PrintfTy);
// Set attributes as per inferLibFuncAttributes in BuildLibCalls.cpp
Function *PrintfF = dyn_cast<Function>(Printf.getCallee());
PrintfF->setDoesNotThrow();
PrintfF->addParamAttr(0, Attribute::NoCapture);
PrintfF->addParamAttr(0, Attribute::ReadOnly);
2: 插入全局变量, 保存 format string: "(llvm-tutor) Hello from: %s\n(llvm-tutor) number of arguments: %d\n"
llvm::ConstantDataArray::getString 构造一个 CDS 并用指定的字符串对它进行初始化。 返回了对应指针。
M.getOrInsertGlobal 插入了一个全局变量, 名为 PrintfFormatStr, 类型与 PrintfFormatStr 相同。
setInitializer 指定了 PrintfFormatStrVar 的初始化值为 PrintfFormatStr 指向的常量。
llvm::Constant *PrintfFormatStr = llvm::ConstantDataArray::getString(
CTX, "(llvm-tutor) Hello from: %s\n(llvm-tutor) number of arguments: %d\n");
Constant *PrintfFormatStrVar =
M.getOrInsertGlobal("PrintfFormatStr", PrintfFormatStr->getType());
dyn_cast<GlobalVariable>(PrintfFormatStrVar)->setInitializer(PrintfFormatStr);
3: 遍历每个 function , 添加对 printf 的调用
这一部分主要是要理解 IRBuilder 的插入点概念和 createFoo 函数的作用
for (auto &F : M) {
// 跳过函数声明, 即对应 Module 中不存在该全局变量的定义
if (F.isDeclaration())
continue;
// 获取 IRBuilder 并设置了插入点在函数的开头
IRBuilder<> Builder(&*F.getEntryBlock().getFirstInsertionPt());
// 创建了一个全局 string 指针, 内空为函数的名称
auto FuncName = Builder.CreateGlobalStringPtr(F.getName());
// 进行了类型转换 [n x i8] -> i8* 数组到指针
llvm::Value *FormatStrPtr =
Builder.CreatePointerCast(PrintfFormatStrVar, PrintfArgTy, "formatStr");
LLVM_DEBUG(dbgs() << " Injecting call to printf inside " << F.getName()
<< "\n");
// 插入函数调用
Builder.CreateCall(
Printf, {FormatStrPtr, FuncName, Builder.getInt32(F.arg_size())});
InsertedAtLeastOnePrintf = true;
}
run()
如果没有修改Module 中的函数, 则之前所有的 analysis 结果都可以保留。 如修改了 Module 中的函数, 并且并没有更新之前的 analysis 结果, 不保留所有的分析结果。
PreservedAnalyses InjectFuncCall::run(llvm::Module &M,
llvm::ModuleAnalysisManager &) {
bool Changed = runOnModule(M);
return (Changed ? llvm::PreservedAnalyses::none()
: llvm::PreservedAnalyses::all());
}