Pass简介——SimplifyCFG
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 节点涉及到不同控制流路径的合并,合并后可能会导致不明确的值或冲突。
- 删除无用的基本块:如果基本块没有前驱(或者只有自己作为前驱),就会被删除,避免冗余的控制流。
显然这个要求是比较粗略的,但是也可能为了防止逻辑过于复杂(更激进的策略)带来的复杂度开销
另一处 MergeBlockIntoPredecessor 位于函数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; }
foldCondBranchOnValueKnownInPredecessorImpl
之中,其主要是在处理 条件分支时 通过常量已知值简化控制流,然后可以合并基本块。
两个函数顾名思义是从后继块和前驱块中提取公共指令,并将它们提升到更合适的位置,以减少冗余(可能的重复部分)。hoistCommonCodeFromSuccessors
,sinkCommonCodeFromPredecessors
常用超参数
PHINodeFoldingThreshold
:PHI 节点折叠 的 启用门槛。它的默认值是 2。tTwoEntryPHINodeFoldingThreshold
: 折叠具有两个入口的 PHI 节点(即只有两个输入值的 PHI 节点)时 可以接受的最大总指令成本,默认值是 4。
上述两个都是启发式调参因子,所以没有直接意义。
评论