SimplifyCFGPass 是 LLVM编译器框架中的一个 控制流图优化(Control Flow Graph, CFG) 相关的 优化 Pass。它的主要作用是 简化控制流图(CFG),减少不必要的基本块(Basic Blocks)和跳转,从而提高指令级并行性、减少分支预测失败、提高编译器生成的代码质量。
其入口为 SimplifyCFGPass. cpp,其主要逻辑位于 SimplifyCFG. cpp。
根据 SimplifyCFG. cpp 的说明,可以知道这个文件主要用途是:

  • 移除没有前驱的基本块。
  • 如果基本块只有一个前驱,且前驱只有一个后继,则将该基本块合并到前驱中。
  • 对于只有一个前驱的基本块,消除 PHI 节点。
  • 消除仅包含无条件跳转的基本块。
  • 将对 nounwind 函数的 invoke 指令转换为 call 指令。
  • 将类似 “if (x) if (y)” 的结构转换为 “if (x&y)”。

代表性函数

MergeBlockIntoPredecessor

这个函数定义于 BasicBlockUtils. cpp,但是在该文件起辅助作用,因为满足时可以使用该函数合并基本块。我们可以看到次级入口函数 simplifyCFG 中有这个函数。当前的允许合并到前驱块的条件是:

  • 前驱只有一个:==只有一个前驱的基本块才会考虑合并==,避免了像循环中的基本块(有多个前驱)被误合并的情况。
  • 前驱只有一个后继:==基本块的前驱只有一个后继时,才进行合并==。这样做是为了避免循环或者分支结构的破坏,确保控制流图的结构不被错误修改。
  • 没有 PHI 节点:==如果基本块中存在 PHI 节点,则不允许合并。==这是因为 PHI 节点涉及到不同控制流路径的合并,合并后可能会导致不明确的值或冲突。
  • 删除无用的基本块:如果基本块没有前驱(或者只有自己作为前驱),就会被删除,避免冗余的控制流。

    显然这个要求是比较粗略的,但是也可能为了防止逻辑过于复杂(更激进的策略)带来的复杂度开销

    bool SimplifyCFGOpt::simplifyOnce(BasicBlock *BB) {
      bool Changed = false;
    
      assert(BB && BB->getParent() && "Block not embedded in function!");
      assert(BB->getTerminator() && "Degenerate basic block encountered!");
    
      // Remove basic blocks that have no predecessors (except the entry block)...
      // or that just have themself as a predecessor.  These are unreachable.
      if ((pred_empty(BB) && BB != &BB->getParent()->getEntryBlock()) ||
          BB->getSinglePredecessor() == BB) {
        LLVM_DEBUG(dbgs() << "Removing BB: \n" << *BB);
        DeleteDeadBlock(BB, DTU);
        return true;
      }
    
      // Check to see if we can constant propagate this terminator instruction
      // away...
      Changed |= ConstantFoldTerminator(BB, /*DeleteDeadConditions=*/true,
                                        /*TLI=*/nullptr, DTU);
    
      // Check for and eliminate duplicate PHI nodes in this block.
      Changed |= EliminateDuplicatePHINodes(BB);
    
      // Check for and remove branches that will always cause undefined behavior.
      if (removeUndefIntroducingPredecessor(BB, DTU, Options.AC))
        return requestResimplify();
    
      // Merge basic blocks into their predecessor if there is only one distinct
      // pred, and if there is only one distinct successor of the predecessor, and
      // if there are no PHI nodes.
      if (MergeBlockIntoPredecessor(BB, DTU))
        return true;
    
      if (SinkCommon && Options.SinkCommonInsts)
        if (sinkCommonCodeFromPredecessors(BB, DTU) ||
            mergeCompatibleInvokes(BB, DTU)) {
          // sinkCommonCodeFromPredecessors() does not automatically CSE PHI's,
          // so we may now how duplicate PHI's.
          // Let's rerun EliminateDuplicatePHINodes() first,
          // before foldTwoEntryPHINode() potentially converts them into select's,
          // after which we'd need a whole EarlyCSE pass run to cleanup them.
          return true;
        }
    
      IRBuilder<> Builder(BB);
    
      if (Options.SpeculateBlocks &&
          !BB->getParent()->hasFnAttribute(Attribute::OptForFuzzing)) {
        // If there is a trivial two-entry PHI node in this basic block, and we can
        // eliminate it, do so now.
        if (auto *PN = dyn_cast<PHINode>(BB->begin()))
          if (PN->getNumIncomingValues() == 2)
            if (foldTwoEntryPHINode(PN, TTI, DTU, Options.AC, DL,
                                    Options.SpeculateUnpredictables))
              return true;
      }
    
      Instruction *Terminator = BB->getTerminator();
      Builder.SetInsertPoint(Terminator);
      switch (Terminator->getOpcode()) {
      case Instruction::Br:
        Changed |= simplifyBranch(cast<BranchInst>(Terminator), Builder);
        break;
      case Instruction::Resume:
        Changed |= simplifyResume(cast<ResumeInst>(Terminator), Builder);
        break;
      case Instruction::CleanupRet:
        Changed |= simplifyCleanupReturn(cast<CleanupReturnInst>(Terminator));
        break;
      case Instruction::Switch:
        Changed |= simplifySwitch(cast<SwitchInst>(Terminator), Builder);
        break;
      case Instruction::Unreachable:
        Changed |= simplifyUnreachable(cast<UnreachableInst>(Terminator));
        break;
      case Instruction::IndirectBr:
        Changed |= simplifyIndirectBr(cast<IndirectBrInst>(Terminator));
        break;
      }
    
      return Changed;
    }
    另一处 MergeBlockIntoPredecessor 位于函数 foldCondBranchOnValueKnownInPredecessorImpl 之中,其主要是在处理 条件分支时 通过常量已知值简化控制流,然后可以合并基本块。
    hoistCommonCodeFromSuccessorssinkCommonCodeFromPredecessors
    两个函数顾名思义是从后继块和前驱块中提取公共指令,并将它们提升到更合适的位置,以减少冗余(可能的重复部分)。

    常用超参数

  • PHINodeFoldingThresholdPHI 节点折叠启用门槛。它的默认值是 2。
  • tTwoEntryPHINodeFoldingThreshold折叠具有两个入口的 PHI 节点(即只有两个输入值的 PHI 节点)时 可以接受的最大总指令成本,默认值是 4。
    上述两个都是启发式调参因子,所以没有直接意义。