如果我试图创建一条指令并在函数的开头插入,这是否是正确的方法,因为当我使用 opt 加载 .so 文件并处理 .ll 文件时,我看不到插入的指令。
if (auto* op = dyn_cast<Instruction>(&I))
{
if(prepend_first == false)
{
llvm::LLVMContext& context = I.getContext();
Value* lhs = ConstantInt::get(Type::getInt32Ty(context), 4);
Value* rhs = ConstantInt::get(Type::getInt32Ty(context), 6);
IRBuilder<> builder(op);
builder.CreateMul(lhs, rhs);
builder.SetInsertPoint(&I);
prepend_first = true;
}
}
编辑(2020 年 11 月 27 日) 这是整个通行证,也是基于其他文章。
using namespace llvm;
namespace
{
struct customllvm : public PassInfoMixin<customllvm>
{
llvm:PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM)
{
for(BasicBlock &BB : F)
{
bool prepend_first = false;
for(Instruction &I : BB)
{
if(auto *op = dyn_cast<Instruction>(&I))
{
if(prepend_first != true)
{
llvm::LLVMContext& context = I.getContext();
Value* lhs = ConstantInt::get(Type::getInt32Ty(context), 4);
Value* rhs = ConstantInt::get(Type::getInt32Ty(context), 6);
IRBuilder<> builder(op);
builder.CreateMul(lhs, rhs);
builder.SetInsertPoint(&I);
prepend_first = true;
}
}
}
}
}
}
}
您需要先设置插入点。然后才创建指令。
这既不是错误插入也不是优化未使用的指令。这都是关于不断折叠的。
它与
SetInsertPoint()
无关,因为IRBuilder<> IRB(&I)
默认情况下在指令I
之前插入新指令。因此,在此示例中,您甚至不需要明确设置插入点.
我将很快说明为什么它与删除未使用的指令无关,但我首先解释另外两个问题你的通行证:
您的通行证不一定按需要在函数的开头just 插入新指令,而是在函数的每个基本块 的开头插入新指令。这里有两个解决方案:
仅在
prepend_first
循环开始之前将false
设置为for(BasicBlock &BB : F)
一次,这样当到达检查prepend_first
语句时,if
仅对第一个基本块为假。由于不必要的迭代,这种方法并不有趣。
更好的方法是通过直接选择第一个基本块的第一条指令来避免对基本块和指令的迭代。我将在我的通行证中使用这种方法。
您的
dyn_cast<Instruction>(&I)
检查演员是没有必要。这个演员表基本上检查I
是否属于Instruction
类型,这显然总是正确的。
这是我的
first_pass
,它的灵感来自于您的代码,但没有上面解释的两个问题:
#include "llvm/IR/IRBuilder.h"
...
PreservedAnalyses first_pass(Function &F) {
Function::iterator FI = F.begin(); //iterator to the first basic block
BasicBlock &BB = *FI;
BasicBlock::iterator BBI = BB.begin(); //iterator to the first instruction
Instruction &I = *BBI;
LLVMContext& context = I.getContext();
Value* lhs = ConstantInt::get(Type::getInt32Ty(context), 4);
Value* rhs = ConstantInt::get(Type::getInt32Ty(context), 6);
IRBuilder<> builder(&I);
builder.CreateMul(lhs, rhs, "new_mul");
return PreservedAnalyses::none();
}
请注意,
first_pass
尚未将常量操作 new_mul
插入输入 IR。在解释作为根本原因的常量折叠之前,让我首先通过一个有趣的实验证明你的问题不是源于删除未使用的代码。
这是我的
test.c
:
int foo(int a, int b) {
int c = a + 1;
int d = b - 2;
return c / d;
}
这是我用来生成
test.ll
的命令,这将是输入 IR:
clang -O0 -Xclang -disable-O0-optnone -emit-llvm -c test.c -o test.bc
opt -S -mem2reg test.bc -o test.ll
这是生成的
test.ll
:
; Function Attrs: noinline nounwind uwtable
define dso_local i32 @foo(i32 noundef %a, i32 noundef %b) #0 {
entry:
%add = add nsw i32 %a, 1
%sub = sub nsw i32 %b, 2
%div = sdiv i32 %add, %sub
ret i32 %div
}
这是我的
second_pass
,它与我的first_pass
相同,不同之处在于它在它创建的第二条指令new_mul
中使用了new_add
的结果,所以这次将使用new_mul
。 new_add
应该在输入IR的第二条指令之前插入:
#include "llvm/IR/IRBuilder.h"
...
PreservedAnalyses second_pass(Function &F) {
Function::iterator FI = F.begin();
BasicBlock &BB = *FI;
BasicBlock::iterator BBI = BB.begin();
Instruction &I = *BBI;
LLVMContext& context = I.getContext();
Value* lhs = ConstantInt::get(Type::getInt32Ty(context), 4);
Value* rhs = ConstantInt::get(Type::getInt32Ty(context), 6);
IRBuilder<> IRB(&I);
auto new_mul = IRB.CreateMul(lhs, rhs, "new_mul");
// Specific to this pass.
Instruction &I_Sec = *(++BBI);
IRBuilder<> IRB_Sec(&I_Sec);
IRB_Sec.CreateAdd(new_mul, I_Sec.getOperand(0), "new_add");
return PreservedAnalyses::none();
}
我通过
test.ll
运行了 second_pass
并得到了这个结果 IR:
; Function Attrs: noinline nounwind uwtable
define dso_local i32 @foo(i32 noundef %a, i32 noundef %b) #0 {
entry:
%add = add nsw i32 %a, 1
%new_add = add i32 24, %b
%sub = sub nsw i32 %b, 2
%div = sdiv i32 %add, %sub
ret i32 %div
}
我们可以看到,虽然这次使用了
new_mul
,但它并没有插入到结果IR中。更有趣的是,new_add
成功插入到结果 IR 中,尽管它没有被使用。 所以很明显,优化掉未使用的代码不能成为 new_mul
没有生成的原因.
回到我的重点:不断折叠!
常量折叠是一种编译器优化,其中在编译时评估常量操作而不是在运行时计算它们。换句话说,编译器不会为常量操作生成代码,如果稍后使用该操作的结果,编译器只会使用它的值。
在您的示例中,您尝试创建的新指令 (
new_mul
) 是一个 constant operation 因为它的两个参数都是常量(4 和 6)。因此,在存在常量折叠的情况下,编译器不会为它生成代码,如果以后使用它(如 new_add
中的second_pass
),编译器将使用它的值(4 * 6 = 24)。
知道
IRBuilder
执行常量折叠非常重要,除非另有明确指定折叠类型。换句话说,如果我们希望编译器不执行常量折叠,我们必须使用 IRBuilder<llvm::NoFolder>
而不是 IRBuilder<>
。
最后,这是我的
third_pass
,它与我的first_pass
相同,不同之处在于它使用IRBuilder<llvm::NoFolder>
而不是IRBuilder<>
来创建常量操作new_mul
:
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/NoFolder.h" // Do not forget to include.
...
PreservedAnalyses third_pass(Function &F) {
Function::iterator FI = F.begin(); //iterator to the first basic block
BasicBlock &BB = *FI;
BasicBlock::iterator BBI = BB.begin(); //iterator to the first instruction
Instruction &I = *BBI;
LLVMContext& context = I.getContext();
Value* lhs = ConstantInt::get(Type::getInt32Ty(context), 4);
Value* rhs = ConstantInt::get(Type::getInt32Ty(context), 6);
IRBuilder<llvm::NoFolder> builder(&I); // NoFolder
builder.CreateMul(lhs, rhs, "new_mul");
return PreservedAnalyses::none();
}
现在如果我运行
test.ll
通过third_pass
我得到这个结果IR:
; Function Attrs: noinline nounwind uwtable
define dso_local i32 @foo(i32 noundef %a, i32 noundef %b) #0 {
entry:
%new_mul = mul i32 4, 6
%add = add nsw i32 %a, 1
%sub = sub nsw i32 %b, 2
%div = sdiv i32 %add, %sub
ret i32 %div
}
正如我们所见,
new_mul
被插入到函数的开头。