【LLVM源码解读】Modifying reassociate for improved CSE

这个patch是我在思考如何判断两个长表达式是否有公共子表达式时发现的。链接在此。该patch的作用主要是实现对一些复杂的例子通过重结合找到其中的公共子表达式。

patch解读

最终融入主branch的代码和上述链接的有出入,以下将会结合代码具体解释。
首先在Reassociate.h中定义了PairMap,这是实现该patch的一个重要的数据结构,此处的定义方式为DenseMap<std::pair<Value *, Value *>, PairMapValue> PairMap[NumBinaryOps]; 其中Map的V对应的类型不只是unsigned,而是自定义的类型,这一点是和上文链接不同的。
PairMapValue的定义如下:

  struct PairMapValue {
    WeakVH Value1;
    WeakVH Value2;
    unsigned Score;
    bool isValid() const { return Value1 && Value2; }
  };

其中,WeakVH是指向Value的一个Handler,可以由此找到对应的Value。此处的Score也即上文链接中的unsigned类型,用于判断某个表达式更可能为公共子表达式。
此处删除了Pass和InstrOpMap。

第二处改动发生在void ReassociatePass::ReassociateExpression(BinaryOperator *I) { 。此处去除了Pass=0,也即第一次使用普通的重结合算法的过程,直接使用判断条件:if (Ops.size() > 2 && Ops.size() <= GlobalReassociateLimit) {,其中 Ops.size() <= GlobalReassociateLimit相当于限制了表达式链的长度,对于过长的表达式及时止损。

	unsigned Max = 1;
    unsigned BestRank = 0;
    std::pair<unsigned, unsigned> BestPair;
    unsigned Idx = I->getOpcode() - Instruction::BinaryOpsBegin;
    unsigned LimitIdx = 0;

Max记录最大的表达式的索引,BestRank记录最大的rank,BestPair记录最佳一组的两个Value对应的序号,Idx是当前指令类型对应的序号。LimitIdx主要限制遍历的下限,如果打开了CSE LocalOpt,那么该值可能发生变化。

接下来是一个二重循环:
两重循环从最后一个指令开始依次比较当前指令和前面的每条指令,分别标记为i和j,每一个组合的开始Score都为0,如果PairMap中已经包含上述的组合,那么将Score加上上述操作的Score。Rank看起来是值编号算法计算的值的排序,应当是越小越好。如果Score比Max大,则将Max更新为当前的Score,并通过BestPair保存当前的组合,将BestRank更新为MaxRank。(如果Score > Max但MaxRank > BestRank,这样的情况是否存在)

for (unsigned i = Ops.size() - 1; i > LimitIdx; --i) {
      // We must use int type to go below zero when LimitIdx is 0.
      for (int j = i - 1; j >= (int)LimitIdx; --j) {
        unsigned Score = 0;
        Value *Op0 = Ops[i].Op;
        Value *Op1 = Ops[j].Op;
        if (std::less<Value *>()(Op1, Op0))
          std::swap(Op0, Op1);
        auto it = PairMap[Idx].find({Op0, Op1});
        if (it != PairMap[Idx].end()) {
          // Functions like BreakUpSubtract() can erase the Values we're using
          // as keys and create new Values after we built the PairMap. There's a
          // small chance that the new nodes can have the same address as
          // something already in the table. We shouldn't accumulate the stored
          // score in that case as it refers to the wrong Value.
          if (it->second.isValid())
            Score += it->second.Score;
        }

        unsigned MaxRank = std::max(Ops[i].Rank, Ops[j].Rank);

        // By construction, the operands are sorted in reverse order of their
        // topological order.
        // So we tend to form (sub) expressions with values that are close to
        // each other.
        //
        // Now to expose more CSE opportunities we want to expose the pair of
        // operands that occur the most (as statically computed in
        // BuildPairMap.) as the first sub-expression.
        //
        // If two pairs occur as many times, we pick the one with the
        // lowest rank, meaning the one with both operands appearing first in
        // the topological order.
        if (Score > Max || (Score == Max && MaxRank < BestRank)) {
          BestPair = {j, i};
          Max = Score;
          BestRank = MaxRank;
        }
      }
    }

上述二重循环完成后,将根据Max的值判断是否有一个组合值得放到当前组合的末尾(可能为公共子表达式的表达式)。

    if (Max > 1) {
      auto Op0 = Ops[BestPair.first];
      auto Op1 = Ops[BestPair.second];
      Ops.erase(&Ops[BestPair.second]);
      Ops.erase(&Ops[BestPair.first]);
      Ops.push_back(Op0);
      Ops.push_back(Op1);
    }

此处使用的操作似乎可以简化(尝试一下)

另外的改动发生在PreservedAnalyses ReassociatePass::run(Function &F, FunctionAnalysisManager &) { 也即该Pass运行的入口函数,作者将其封装为void
ReassociatePass::BuildPairMap(ReversePostOrderTraversal<Function *> &RPOT) {。该函数构建了PairMap,首先按照PROT顺序,也即ReversePostOrder(反转后向序)遍历各基本块,对于其中的每条指令,如果是满足结合律或者是二元表达式,则暂不处理。如果该表达式不是树的根节点,也暂不处理。
建立一个WorkList,初始只包含指令的两个操作数,建立一个Ops,然后使用迭代算法不断探索更多可以重结合的可能,循环的结束条件是WorkList为空或Ops达到最大限制(理由同上,避免表达式过长造成程序占用资源过多)。找到当前Value对应的Instruction,如果该Instruction不存在,或是两个指令的操作符不同(也即不能重结合,此处似乎可以判断两个表达式之间不同但可以重结合的可能),或是对应的Instruction有多个Use。直接放入Ops不做任何操作(也即该值相关的操作已经完成),否则将对应指令的操作数放入WorkList。
当该循环结束时,可能有两种情况,一种是由于WorkList为空,也即操作结束了。一种是由于Ops.size() > GlobalReassociateLimit,也即超出了算法所能分析的长度,如果是第二种,则不够减PairMap。
下面执行的操作和上面分析的算法类似,也是一个二重循环,Visited记录了处理过的Value对,在处理前先要记录,并且在记录成功的情况下才会执行处理(通过

if (!Visited.insert({Op0, Op1}).second)
            continue;

来判断),在PairMap中插入一条记录,对应的Score为1.
如果此次插入失败,同样有处理逻辑。

void
ReassociatePass::BuildPairMap(ReversePostOrderTraversal<Function *> &RPOT) {
  // Make a "pairmap" of how often each operand pair occurs.
  for (BasicBlock *BI : RPOT) {
    for (Instruction &I : *BI) {
      if (!I.isAssociative() || !I.isBinaryOp())
        continue;

      // Ignore nodes that aren't at the root of trees.
      if (I.hasOneUse() && I.user_back()->getOpcode() == I.getOpcode())
        continue;

      // Collect all operands in a single reassociable expression.
      // Since Reassociate has already been run once, we can assume things
      // are already canonical according to Reassociation's regime.
      SmallVector<Value *, 8> Worklist = { I.getOperand(0), I.getOperand(1) };
      SmallVector<Value *, 8> Ops;
      while (!Worklist.empty() && Ops.size() <= GlobalReassociateLimit) {
        Value *Op = Worklist.pop_back_val();
        Instruction *OpI = dyn_cast<Instruction>(Op);
        if (!OpI || OpI->getOpcode() != I.getOpcode() || !OpI->hasOneUse()) {
          Ops.push_back(Op);
          continue;
        }
        // Be paranoid about self-referencing expressions in unreachable code.
        if (OpI->getOperand(0) != OpI)
          Worklist.push_back(OpI->getOperand(0));
        if (OpI->getOperand(1) != OpI)
          Worklist.push_back(OpI->getOperand(1));
      }
      // Skip extremely long expressions.
      if (Ops.size() > GlobalReassociateLimit)
        continue;

      // Add all pairwise combinations of operands to the pair map.
      unsigned BinaryIdx = I.getOpcode() - Instruction::BinaryOpsBegin;
      SmallSet<std::pair<Value *, Value*>, 32> Visited;
      for (unsigned i = 0; i < Ops.size() - 1; ++i) {
        for (unsigned j = i + 1; j < Ops.size(); ++j) {
          // Canonicalize operand orderings.
          Value *Op0 = Ops[i];
          Value *Op1 = Ops[j];
          if (std::less<Value *>()(Op1, Op0))
            std::swap(Op0, Op1);
          if (!Visited.insert({Op0, Op1}).second)
            continue;
          auto res = PairMap[BinaryIdx].insert({{Op0, Op1}, {Op0, Op1, 1}});
          if (!res.second) {
            // If either key value has been erased then we've got the same
            // address by coincidence. That can't happen here because nothing is
            // erasing values but it can happen by the time we're querying the
            // map.
            assert(res.first->second.isValid() && "WeakVH invalidated");
            ++res.first->second.Score;
          }
        }
      }
    }
  }
}

调试作者的例子

贴一下作者的代码:

void foo(int a, int b, int c, int d, int e, int *res) {
  res[0] = (e * a) * d;
  res[1] = (e * b) * d;
  res[2] = (e * c) * d;
}

这个例子的巧妙之处是参数中有一个res,表面上看该函数没有返回值,如果使用函数返回值优化将会删除所有指令,但由于有res的参与,该函数是可以对caller造成影响的,因此从返回值的角度不能删除任何一条指令。

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于在Linux上编译LLVM源代码的基本步骤。请注意,由于不同的Linux发行版可能有不同的安装要求,所以下面的步骤可能需要根据您的特定情况进行调整。 1. 首先,您需要安装一些必要的依赖项,例如CMake、GCC、Python等。您可以使用以下命令在Ubuntu上安装这些依赖项: ```sh sudo apt-get install cmake build-essential python ``` 2. 下载最新版本的LLVM源代码。您可以在LLVM官方网站(https://llvm.org/)上找到源代码的下载链接。例如,您可以使用以下命令从LLVM官方网站上下载最新版本的源代码: ```sh wget https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/llvm-project-12.0.1.tar.xz ``` 3. 解压缩下载的源代码包并进入解压后的目录: ```sh tar -xf llvm-project-12.0.1.tar.xz cd llvm-project-12.0.1 ``` 4. 创建一个构建目录并进入该目录: ```sh mkdir build cd build ``` 5. 运行CMake,指定LLVM源代码的路径和其他配置选项。例如,您可以使用以下命令启动CMake: ```sh cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang;lld" -DCMAKE_BUILD_TYPE=Release ../llvm ``` 6. 运行make命令来编译LLVM和其他组件: ```sh make -j8 ``` 在这个命令中,“-j8”选项表示使用8个并行处理器编译代码。您可以根据您的机器配置进行调整。 7. 安装编译后的LLVM和其他组件: ```sh sudo make install ``` 完成上述步骤后,您应该能够在Linux上成功编译LLVM源代码。请注意,这只是一个基本的指南,您可能需要根据您的特定情况进行其他调整或安装其他依赖项。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值