【LLVM Pass解读】Reassociate 重结合优化

run函数的分析

首先,ReassociatePass是一个FunctionAnalysis,所以其入口函数为
PreservedAnalyses ReassociatePass::run(Function &F, FunctionAnalysisManager &) {

  1. 首先对一个函数的基本块构造ReversePostOrderTraversal,该顺序用于BuildRankMap中,以pre calculate ranks,得到的Traversal中去除了不可达基本块,因为这会导致重结合"hang"。
  2. 计算rankMap:BuildRankMap(F, PROT)
  3. 计算PairMap:BuildPairMap(PROT)。该过程从理论上来说应该在执行完一轮重结合后使用,但是这样会增加编译开销,且在real-world代码中帮助不大;此外在运行时更新pairMap是可能的,但如果重结合链过长可能会造成过大开销。
  4. 以RPOT顺序遍历基本块,从上到下遍历基本块中的每条指令,如果指令是TriviallyDead,就删除之,否则调用OptimizeInst(注意不能将该指令移动基本块,否则I++无法正确找到基本块)
  5. 创建一个RedoInsts的列表ToRedo(该Insts似乎是需要删除的指令集合),对于每个指令,判断是否是dead的,如果是,执行RecursivelyEraseDeadInsts, MadeChange设为true。结束上述操作后,如果RedoInsts曾经不为空,考察每条指令,如果该指令是TriviallyDead,EraseInst)否则OptimizeInst。
  6. 完成后,RankMap和ValueRankMap以及PairMap都清空,如果发生了指令的删除,重新进行CFG分析。
    上述过程整理成图:
    在这里插入图片描述
    可以看到上述过程中对Inst的删除操作是重复的,但由于保证安全删除的判断条件能够避免二次删除操作,所以此处并没有报错。
    似乎只有一种可能,如果RedoInst的指令一边删除一边Optimize会有影响,需要将所有的都删除后再将剩余的OptimizeInst。如果这种可能不存在,则当前这种两轮循环的操作就是多余的。
    但是这种操作在编译器中其实是很常见的,一方面有assert的重复判定,另一方面有诸多重复操作的进行,是不可避免且无需指摘的,但是可以研究是否存在某个例子满足此种情况,这说明如果贸然将两个循环进行合并会造成在该例子下的错误。

BuildRankMap

初始Rank值为2(好神奇的数字),所有Argument依次为2, 3等等,存入ValueRankMap。PROT顺序遍历所有基本块,每个基本块的Rank分别为++Rank << 16(似乎某个Rank被浪费了,but无伤大雅,左移16位避免和其他的编号重复)。
对于mayHaveNonDefUseDependency的指令,将其记录到ValueRankMap中,看注释是说不能移动的指令,具体细节可以参考笔者较新的文章。
正常情况下,ValueRankMap中只记录了函数的参数,RankMap中只记录了BB。

BuildPairMap

PairMap是我另一篇文章中的重要数据结构,根据其中记录的信息决定对长的表达式进行重新组织。此处记录较为简略:
首先跳过不满足结合律的、不是二元操作的、只有一个Use的,或者最后一个Use是当前指令的(好奇怪,看起来是不满足先定义后使用)。
初始Worklist只有两个操作数,依次判断操作数的定义指令,如果操作数的定义指令类型和当前指令类型不同(即只看这两个指令不能交换)或定义指令有多个Use(此处没看懂)则将其直接添加到Ops中,继续新的判定。
否则判断是否是自引用指令(在SSA中似乎不满足此类型),也即a = a + 2类似的。如果是自引用的话,贸然将其加入Worklist会造成死循环。
如果正常结束(也即检测到的指令数目不超过限制),获取BinaryIndex,然后依次遍历Ops中的变量,每个变量都和他后面的各个变量依次比较,插入到PairMap中,并将对应number设为1,表示遇到了一次该变量组合。同时由于Visited的存在,每个变量组合只会插入一次。

OptimizeInst

  1. 首先判断是否是一元或二元运算符
  2. 如果是Shift且移位运算数是常数,如果shift的use是用于可重结合的add,则将其转化为乘法指令。
  3. 如果一个指令是可交换的,对其两个操作数进行排序
  4. 如果指令包含负的浮点常数,将其转化为另一种形式:
    /// OtherOp + (subtree) -> OtherOp {+/-} (canonical subtree)
    /// (subtree) + OtherOp -> OtherOp {+/-} (canonical subtree)
    /// OtherOp - (subtree) -> OtherOp {+/-} (canonical subtree)
  5. 对于浮点数指令,如果没有FPAssociativeFlags,则不进行重结合优化。
  6. 不优化bool指令,以暴露更多优化机会
  7. // If this is a bitwise or instruction of operands
    // with no common bits set, convert it to X+Y. 不是很懂
  8. 下面是对于x-y统一为x±y
  9. 处理associative binary operator,
  10. 处理interior node
  11. 先add后sub的情况,跳过对add的分析,留待之后的sub处理。
  12. 执行ReassociateExpression

可以看到,上述操作其实更多的是对指令的预处理和一些特殊情况的跳过。

ReassociateExpression

  1. 调用LinearizeExprTree,构造Tree,线性化。
  2. 完成上述操作后,Tree数组会保存一系列RepeatedValue,可以用来构造待处理的Ops数组。
  3. (-X)Y + Z -> Z-XY进行如左的处理。
  4. 如果只有一个Op,如何处理
  5. 如果有多个Op,相应的处理
  6. RewriteExprTree

RewriteExprTree

参数为I——Instruction, Ops——存储操作数的数组,HasNUW. 与LinearizeExprTree类似。该函数是将排好序并优化好的表达式集合重新写入到expression tree中,删除不需要的结点。

  1. NodesToRewrite数组,记录需要重写的指令集合。
  2. NotRewritable记录所有不能重写的变量集合,也即Ops中所有的变量。
  3. 处理Ops中的每个Op,对于最后一个的处理和其他的不同,首先判断是否不变,若不变则break;如果是两个交换,则交换之,否则要重新构建。
  4. 对于非last指令,首先判断右手边是不是Ops现在的元素。
  5. 处理lhs,如果BO是重结合的操作数且不在NoRewritable数组中,则将其赋值给Op,并进行下一轮处理。
  6. 如果NodesToRewrite为空,构造一个Undef作为NewOp,否则取出最后一个元素。
  7. 如果ExpressionChangedStart不为空,说明开始执行修改表达式的操作,循环执行。
  8. 如果ClearFlags,针对FPMathOperator,获取其FastMathFlags,将其赋给ExpressionChangedStart。
  9. 如果是普通指令,判断是否有HasNUW,将其进行设置。
  10. 如果ExpressionChangedStart和ExpressionChangedEnd相同,则将clearFlags设置为false,如果ExpressionChangedStart和当前指令相同,则直接跳出。
  11. 在此情况下如果clearFlags仍然为true,执行replaceDbgUsesWithUndef。
  12. ExpressionChangedStart->moveBefore(I);
  13. 修改ExpressionChangedStart

LinearizeExprTree

unsigned Bitwidth = I->getType()->getScalarType()->getPrimitiveSizeInBits();

  1. 首先获取位宽,用于构建Worklist中的元素,HasNUW表示指令是否是溢出的二元表达式。
  2. 执行一个While循环,判断条件为Worklist是否为空,首先取出指令,判断是否是溢出的。对指令的每个操作数进行处理。
  3. 如果操作数是可重结合的,将其加入Worklist
  4. 判断操作数是否已经在Leaves数组中,如果没有,判断是否有多个Use,如果有,则需要将其标记为叶子结点,如果只有一个use,则不处理。
  5. 如果当前操作数不在Leaves数组中,首先更新其对应的Weight,如果有Use,则进行新一轮的处理(处理下一个操作数或结束),将Weight修改为更新后的值,从Leaves中移除该值。
  6. 对于乘法指令,将其进行乘-1处理并传播。
  7. 之后是构造Ops的操作。

RecursivelyEraseDeadInst

  1. 记录所有操作数(如果该操作数只有此处一个use,那么该指令删除后,当前操作数的定义指令也是dead的,也可以删除)
  2. 从ValueRankMap,Insts(也即上文的ToRedo),RedoInsts中删除,然后删除当前Instruction。
  3. 考察各操作数是否只有此处一个use,如果是,则删除之。
  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
污点分析是一种静态程序分析技术,用于确定程序中哪些变量可以被恶意输入或其他安全漏洞所利用。LLVM是一个广泛使用的编译器基础设施,可以用于实现污点分析。下面是一个简单的LLVM Pass,它实现了简单的污点分析。 首先,我们需要定义一个Pass类,该类继承自llvm::FunctionPass。然后,我们需要在runOnFunction函数中实现我们的污点分析逻辑。在这个例子中,我们将通过检查函数的参数和指令来确定哪些变量是受污染的。 ```c++ #include "llvm/IR/Function.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" #include "llvm/IR/Instructions.h" using namespace llvm; namespace { struct TaintAnalysis : public FunctionPass { static char ID; TaintAnalysis() : FunctionPass(ID) {} bool runOnFunction(Function &F) override { // 遍历函数的所有基本块 for (auto &BB : F) { // 遍历基本块的所有指令 for (auto &I : BB) { // 如果指令是一个存储指令 if (auto *SI = dyn_cast<StoreInst>(&I)) { // 如果存储指令的源操作数是一个指针类型 if (auto *Ptr = dyn_cast<PointerType>(SI->getOperand(1)->getType())) { // 如果指针指向的类型是整数类型 if (auto *IntTy = dyn_cast<IntegerType>(Ptr->getElementType())) { // 如果整数类型的位宽为8 if (IntTy->getBitWidth() == 8) { // 输出受污染的指针值和存储的值 errs() << "Tainted pointer value: " << *SI->getOperand(1) << "\n"; errs() << "Tainted value: " << *SI->getOperand(0) << "\n"; } } } } } } return false; } }; } char TaintAnalysis::ID = 0; static RegisterPass<TaintAnalysis> X("taint-analysis", "Taint Analysis Pass"); ``` 我们在runOnFunction函数中遍历函数的所有基本块和指令。我们检查每个存储指令,以确定它是否存储了一个指向整数类型的指针,并且该整数类型具有8位的位宽。如果是的话,我们输出受污染的指针值和存储的值。 最后,我们将该Pass注册到LLVM中,以便在编译时运行。我们使用static RegisterPass来注册我们的Pass,并将其命名为“taint-analysis”。 现在,我们可以使用LLVM编译器运行我们的Pass,以便对C或C++程序进行污点分析。例如,假设我们有以下C程序: ```c++ #include <stdio.h> void foo(int *ptr) { int x = *ptr; printf("The value of x is: %d\n", x); } int main() { int y = 42; foo(&y); return 0; } ``` 我们可以使用以下命令编译程序并运行我们的Pass: ``` clang -Xclang -load -Xclang MyPass.so -c test.c ``` 这将生成一个名为“test.o”的目标文件,并使用我们的Pass进行污点分析。如果程序中存在受污染的指针,我们的Pass将输出它们的值。在这个例子中,我们应该得到以下输出: ``` Tainted pointer value: i32* %ptr Tainted value: i32 42 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值