| //===-- WebAssemblyCFGSort.cpp - CFG Sorting ------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// This file implements a CFG sorting pass. | |
| /// | |
| /// This pass reorders the blocks in a function to put them into topological | |
| /// order, ignoring loop backedges, and without any loop or exception being | |
| /// interrupted by a block not dominated by the its header, with special care | |
| /// to keep the order as similar as possible to the original order. | |
| /// | |
| ////===----------------------------------------------------------------------===// | |
| #include "WebAssembly.h" | |
| #include "WebAssemblyExceptionInfo.h" | |
| #include "WebAssemblySortRegion.h" | |
| #include "WebAssemblyUtilities.h" | |
| #include "llvm/ADT/PriorityQueue.h" | |
| #include "llvm/CodeGen/MachineDominators.h" | |
| #include "llvm/CodeGen/MachineFunction.h" | |
| #include "llvm/CodeGen/MachineFunctionAnalysisManager.h" | |
| #include "llvm/CodeGen/MachineLoopInfo.h" | |
| #include "llvm/CodeGen/MachinePassManager.h" | |
| #include "llvm/CodeGen/MachineRegisterInfo.h" | |
| #include "llvm/CodeGen/Passes.h" | |
| #include "llvm/IR/Analysis.h" | |
| #include "llvm/Support/Debug.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| using namespace llvm; | |
| using WebAssembly::SortRegion; | |
| using WebAssembly::SortRegionInfo; | |
| #define DEBUG_TYPE "wasm-cfg-sort" | |
| // Option to disable EH pad first sorting. Only for testing unwind destination | |
| // mismatches in CFGStackify. | |
| static cl::opt<bool> WasmDisableEHPadSort( | |
| "wasm-disable-ehpad-sort", cl::ReallyHidden, | |
| cl::desc( | |
| "WebAssembly: Disable EH pad-first sort order. Testing purpose only."), | |
| cl::init(false)); | |
| namespace { | |
| class WebAssemblyCFGSortLegacy final : public MachineFunctionPass { | |
| StringRef getPassName() const override { return "WebAssembly CFG Sort"; } | |
| void getAnalysisUsage(AnalysisUsage &AU) const override { | |
| AU.setPreservesCFG(); | |
| AU.addRequired<MachineDominatorTreeWrapperPass>(); | |
| AU.addRequired<MachineLoopInfoWrapperPass>(); | |
| AU.addRequired<WebAssemblyExceptionInfoWrapperPass>(); | |
| MachineFunctionPass::getAnalysisUsage(AU); | |
| } | |
| bool runOnMachineFunction(MachineFunction &MF) override; | |
| public: | |
| static char ID; // Pass identification, replacement for typeid | |
| WebAssemblyCFGSortLegacy() : MachineFunctionPass(ID) {} | |
| }; | |
| } // end anonymous namespace | |
| char WebAssemblyCFGSortLegacy::ID = 0; | |
| INITIALIZE_PASS(WebAssemblyCFGSortLegacy, DEBUG_TYPE, | |
| "Reorders blocks in topological order", false, false) | |
| FunctionPass *llvm::createWebAssemblyCFGSortLegacyPass() { | |
| return new WebAssemblyCFGSortLegacy(); | |
| } | |
| static void maybeUpdateTerminator(MachineBasicBlock *MBB) { | |
| #ifndef NDEBUG | |
| bool AnyBarrier = false; | |
| #endif | |
| bool AllAnalyzable = true; | |
| for (const MachineInstr &Term : MBB->terminators()) { | |
| #ifndef NDEBUG | |
| AnyBarrier |= Term.isBarrier(); | |
| #endif | |
| AllAnalyzable &= Term.isBranch() && !Term.isIndirectBranch(); | |
| } | |
| assert((AnyBarrier || AllAnalyzable) && | |
| "analyzeBranch needs to analyze any block with a fallthrough"); | |
| // Find the layout successor from the original block order. | |
| MachineFunction *MF = MBB->getParent(); | |
| MachineBasicBlock *OriginalSuccessor = | |
| unsigned(MBB->getNumber() + 1) < MF->getNumBlockIDs() | |
| ? MF->getBlockNumbered(MBB->getNumber() + 1) | |
| : nullptr; | |
| if (AllAnalyzable) | |
| MBB->updateTerminator(OriginalSuccessor); | |
| } | |
| namespace { | |
| // EH pads are selected first regardless of the block comparison order. | |
| // When only one of the BBs is an EH pad, we give a higher priority to it, to | |
| // prevent common mismatches between possibly throwing calls and ehpads they | |
| // unwind to, as in the example below: | |
| // | |
| // bb0: | |
| // call @foo // If this throws, unwind to bb2 | |
| // bb1: | |
| // call @bar // If this throws, unwind to bb3 | |
| // bb2 (ehpad): | |
| // handler_bb2 | |
| // bb3 (ehpad): | |
| // handler_bb3 | |
| // continuing code | |
| // | |
| // Because this pass tries to preserve the original BB order, this order will | |
| // not change. But this will result in this try-catch structure in CFGStackify, | |
| // resulting in a mismatch: | |
| // try | |
| // try | |
| // call @foo | |
| // call @bar // This should unwind to bb3, not bb2! | |
| // catch | |
| // handler_bb2 | |
| // end | |
| // catch | |
| // handler_bb3 | |
| // end | |
| // continuing code | |
| // | |
| // If we give a higher priority to an EH pad whenever it is ready in this | |
| // example, when both bb1 and bb2 are ready, we would pick up bb2 first. | |
| /// Sort blocks by their number. | |
| struct CompareBlockNumbers { | |
| bool operator()(const MachineBasicBlock *A, | |
| const MachineBasicBlock *B) const { | |
| if (!WasmDisableEHPadSort) { | |
| if (A->isEHPad() && !B->isEHPad()) | |
| return false; | |
| if (!A->isEHPad() && B->isEHPad()) | |
| return true; | |
| } | |
| return A->getNumber() > B->getNumber(); | |
| } | |
| }; | |
| /// Sort blocks by their number in the opposite order.. | |
| struct CompareBlockNumbersBackwards { | |
| bool operator()(const MachineBasicBlock *A, | |
| const MachineBasicBlock *B) const { | |
| if (!WasmDisableEHPadSort) { | |
| if (A->isEHPad() && !B->isEHPad()) | |
| return false; | |
| if (!A->isEHPad() && B->isEHPad()) | |
| return true; | |
| } | |
| return A->getNumber() < B->getNumber(); | |
| } | |
| }; | |
| /// Bookkeeping for a region to help ensure that we don't mix blocks not | |
| /// dominated by the its header among its blocks. | |
| struct Entry { | |
| const SortRegion *TheRegion; | |
| unsigned NumBlocksLeft; | |
| /// List of blocks not dominated by Loop's header that are deferred until | |
| /// after all of Loop's blocks have been seen. | |
| std::vector<MachineBasicBlock *> Deferred; | |
| explicit Entry(const SortRegion *R) | |
| : TheRegion(R), NumBlocksLeft(R->getNumBlocks()) {} | |
| }; | |
| } // end anonymous namespace | |
| /// Sort the blocks, taking special care to make sure that regions are not | |
| /// interrupted by blocks not dominated by their header. | |
| /// TODO: There are many opportunities for improving the heuristics here. | |
| /// Explore them. | |
| static void sortBlocks(MachineFunction &MF, const MachineLoopInfo &MLI, | |
| const WebAssemblyExceptionInfo &WEI, | |
| MachineDominatorTree &MDT) { | |
| // Remember original layout ordering, so we can update terminators after | |
| // reordering to point to the original layout successor. | |
| MF.RenumberBlocks(); | |
| // Prepare for a topological sort: Record the number of predecessors each | |
| // block has, ignoring loop backedges. | |
| SmallVector<unsigned, 16> NumPredsLeft(MF.getNumBlockIDs(), 0); | |
| for (MachineBasicBlock &MBB : MF) { | |
| unsigned N = MBB.pred_size(); | |
| if (MachineLoop *L = MLI.getLoopFor(&MBB)) | |
| if (L->getHeader() == &MBB) | |
| for (const MachineBasicBlock *Pred : MBB.predecessors()) | |
| if (L->contains(Pred)) | |
| --N; | |
| NumPredsLeft[MBB.getNumber()] = N; | |
| } | |
| // Topological sort the CFG, with additional constraints: | |
| // - Between a region header and the last block in the region, there can be | |
| // no blocks not dominated by its header. | |
| // - It's desirable to preserve the original block order when possible. | |
| // We use two ready lists; Preferred and Ready. Preferred has recently | |
| // processed successors, to help preserve block sequences from the original | |
| // order. Ready has the remaining ready blocks. EH blocks are picked first | |
| // from both queues. | |
| PriorityQueue<MachineBasicBlock *, std::vector<MachineBasicBlock *>, | |
| CompareBlockNumbers> | |
| Preferred; | |
| PriorityQueue<MachineBasicBlock *, std::vector<MachineBasicBlock *>, | |
| CompareBlockNumbersBackwards> | |
| Ready; | |
| SortRegionInfo SRI(MLI, WEI); | |
| SmallVector<Entry, 4> Entries; | |
| for (MachineBasicBlock *MBB = &MF.front();;) { | |
| const SortRegion *R = SRI.getRegionFor(MBB); | |
| if (R) { | |
| // If MBB is a region header, add it to the active region list. We can't | |
| // put any blocks that it doesn't dominate until we see the end of the | |
| // region. | |
| if (R->getHeader() == MBB) | |
| Entries.push_back(Entry(R)); | |
| // For each active region the block is in, decrement the count. If MBB is | |
| // the last block in an active region, take it off the list and pick up | |
| // any blocks deferred because the header didn't dominate them. | |
| for (Entry &E : Entries) | |
| if (E.TheRegion->contains(MBB) && --E.NumBlocksLeft == 0) | |
| for (auto *DeferredBlock : E.Deferred) | |
| Ready.push(DeferredBlock); | |
| while (!Entries.empty() && Entries.back().NumBlocksLeft == 0) | |
| Entries.pop_back(); | |
| } | |
| // The main topological sort logic. | |
| for (MachineBasicBlock *Succ : MBB->successors()) { | |
| // Ignore backedges. | |
| if (MachineLoop *SuccL = MLI.getLoopFor(Succ)) | |
| if (SuccL->getHeader() == Succ && SuccL->contains(MBB)) | |
| continue; | |
| // Decrement the predecessor count. If it's now zero, it's ready. | |
| if (--NumPredsLeft[Succ->getNumber()] == 0) | |
| Preferred.push(Succ); | |
| } | |
| // Determine the block to follow MBB. First try to find a preferred block, | |
| // to preserve the original block order when possible. | |
| MachineBasicBlock *Next = nullptr; | |
| while (!Preferred.empty()) { | |
| Next = Preferred.top(); | |
| Preferred.pop(); | |
| // If X isn't dominated by the top active region header, defer it until | |
| // that region is done. | |
| if (!Entries.empty() && | |
| !MDT.dominates(Entries.back().TheRegion->getHeader(), Next)) { | |
| Entries.back().Deferred.push_back(Next); | |
| Next = nullptr; | |
| continue; | |
| } | |
| // If Next was originally ordered before MBB, and it isn't because it was | |
| // loop-rotated above the header, it's not preferred. | |
| if (Next->getNumber() < MBB->getNumber() && | |
| (WasmDisableEHPadSort || !Next->isEHPad()) && | |
| (!R || !R->contains(Next) || | |
| R->getHeader()->getNumber() < Next->getNumber())) { | |
| Ready.push(Next); | |
| Next = nullptr; | |
| continue; | |
| } | |
| break; | |
| } | |
| // If we didn't find a suitable block in the Preferred list, check the | |
| // general Ready list. | |
| if (!Next) { | |
| // If there are no more blocks to process, we're done. | |
| if (Ready.empty()) { | |
| maybeUpdateTerminator(MBB); | |
| break; | |
| } | |
| for (;;) { | |
| Next = Ready.top(); | |
| Ready.pop(); | |
| // If Next isn't dominated by the top active region header, defer it | |
| // until that region is done. | |
| if (!Entries.empty() && | |
| !MDT.dominates(Entries.back().TheRegion->getHeader(), Next)) { | |
| Entries.back().Deferred.push_back(Next); | |
| continue; | |
| } | |
| break; | |
| } | |
| } | |
| // Move the next block into place and iterate. | |
| Next->moveAfter(MBB); | |
| maybeUpdateTerminator(MBB); | |
| MBB = Next; | |
| } | |
| assert(Entries.empty() && "Active sort region list not finished"); | |
| MF.RenumberBlocks(); | |
| #ifndef NDEBUG | |
| for (auto &MBB : MF) { | |
| assert(MBB.getNumber() >= 0 && "Renumbered blocks should be non-negative."); | |
| const SortRegion *Region = SRI.getRegionFor(&MBB); | |
| if (Region && &MBB == Region->getHeader()) { | |
| // Region header. | |
| if (Region->isLoop()) { | |
| // Loop header. The loop predecessor should be sorted above, and the | |
| // other predecessors should be backedges below. | |
| for (auto *Pred : MBB.predecessors()) | |
| assert( | |
| (Pred->getNumber() < MBB.getNumber() || Region->contains(Pred)) && | |
| "Loop header predecessors must be loop predecessors or " | |
| "backedges"); | |
| } else { | |
| // Exception header. All predecessors should be sorted above. | |
| for (auto *Pred : MBB.predecessors()) | |
| assert(Pred->getNumber() < MBB.getNumber() && | |
| "Non-loop-header predecessors should be topologically sorted"); | |
| } | |
| } else { | |
| // Not a region header. All predecessors should be sorted above. | |
| for (auto *Pred : MBB.predecessors()) | |
| assert(Pred->getNumber() < MBB.getNumber() && | |
| "Non-loop-header predecessors should be topologically sorted"); | |
| } | |
| } | |
| SmallPtrSet<const SortRegion *, 8> Regions; | |
| for (auto &MBB : MF) { | |
| const SortRegion *Region = SRI.getRegionFor(&MBB); | |
| if (Region) | |
| Regions.insert(Region); | |
| } | |
| SmallVector<std::pair<int, int>, 8> RegionIntervals(Regions.size(), {-1, -1}); | |
| unsigned RegionIdx = 0; | |
| for (auto *Region : Regions) { | |
| assert(Region->getHeader() != &MF.front() && | |
| "The function entry block shouldn't actually be a region header"); | |
| auto *Header = Region->getHeader(); | |
| auto *Bottom = SRI.getBottom(Region); | |
| assert(Header && "Regions must have a header"); | |
| assert(Bottom && "Regions must have a bottom"); | |
| std::pair<int, int> Interval = {Header->getNumber(), Bottom->getNumber()}; | |
| assert(Interval.first <= Interval.second && | |
| "Region bottoms must be sorted after region headers"); | |
| RegionIntervals[RegionIdx++] = Interval; | |
| for (auto *MBB : Region->blocks()) { | |
| assert(MBB->getNumber() >= Interval.first && | |
| MBB->getNumber() <= Interval.second && | |
| "All blocks within a region must have numbers within the region's " | |
| "interval"); | |
| } | |
| } | |
| for (const auto &IntervalA : RegionIntervals) { | |
| for (const auto &IntervalB : RegionIntervals) { | |
| auto AContainsB = IntervalA.first <= IntervalB.first && | |
| IntervalA.second >= IntervalB.second; | |
| auto BContainsA = IntervalB.first <= IntervalA.first && | |
| IntervalB.second >= IntervalA.second; | |
| auto Disjoint = IntervalA.second < IntervalB.first || | |
| IntervalA.first > IntervalB.second; | |
| assert((AContainsB || BContainsA || Disjoint) && | |
| "Regions must be fully contained within their parents and not " | |
| "overlap their siblings"); | |
| } | |
| } | |
| #endif | |
| } | |
| static bool sortCFG(MachineFunction &MF, MachineLoopInfo &MLI, | |
| WebAssemblyExceptionInfo &WEI, MachineDominatorTree &MDT) { | |
| LLVM_DEBUG(dbgs() << "********** CFG Sorting **********\n" | |
| "********** Function: " | |
| << MF.getName() << '\n'); | |
| // Liveness is not tracked for VALUE_STACK physreg. | |
| MF.getRegInfo().invalidateLiveness(); | |
| // Sort the blocks, with contiguous sort regions. | |
| sortBlocks(MF, MLI, WEI, MDT); | |
| return true; | |
| } | |
| bool WebAssemblyCFGSortLegacy::runOnMachineFunction(MachineFunction &MF) { | |
| MachineLoopInfo &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI(); | |
| WebAssemblyExceptionInfo &WEI = | |
| getAnalysis<WebAssemblyExceptionInfoWrapperPass>().getWEI(); | |
| MachineDominatorTree &MDT = | |
| getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree(); | |
| return sortCFG(MF, MLI, WEI, MDT); | |
| } | |
| PreservedAnalyses | |
| WebAssemblyCFGSortPass::run(MachineFunction &MF, | |
| MachineFunctionAnalysisManager &MFAM) { | |
| MachineLoopInfo &MLI = MFAM.getResult<MachineLoopAnalysis>(MF); | |
| WebAssemblyExceptionInfo &WEI = | |
| MFAM.getResult<WebAssemblyExceptionAnalysis>(MF); | |
| MachineDominatorTree &MDT = MFAM.getResult<MachineDominatorTreeAnalysis>(MF); | |
| return sortCFG(MF, MLI, WEI, MDT) ? getMachineFunctionPassPreservedAnalyses() | |
| .preserveSet<CFGAnalyses>() | |
| : PreservedAnalyses::all(); | |
| } |