| 1 | //===-- WebAssemblyCFGSort.cpp - CFG Sorting ------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// |
| 9 | /// \file |
| 10 | /// This file implements a CFG sorting pass. |
| 11 | /// |
| 12 | /// This pass reorders the blocks in a function to put them into topological |
| 13 | /// order, ignoring loop backedges, and without any loop or exception being |
| 14 | /// interrupted by a block not dominated by the its header, with special care |
| 15 | /// to keep the order as similar as possible to the original order. |
| 16 | /// |
| 17 | ////===----------------------------------------------------------------------===// |
| 18 | |
| 19 | #include "WebAssembly.h" |
| 20 | #include "WebAssemblyExceptionInfo.h" |
| 21 | #include "WebAssemblySortRegion.h" |
| 22 | #include "WebAssemblyUtilities.h" |
| 23 | #include "llvm/ADT/PriorityQueue.h" |
| 24 | #include "llvm/ADT/SetVector.h" |
| 25 | #include "llvm/CodeGen/MachineDominators.h" |
| 26 | #include "llvm/CodeGen/MachineFunction.h" |
| 27 | #include "llvm/CodeGen/MachineLoopInfo.h" |
| 28 | #include "llvm/CodeGen/MachineRegisterInfo.h" |
| 29 | #include "llvm/CodeGen/Passes.h" |
| 30 | #include "llvm/CodeGen/WasmEHFuncInfo.h" |
| 31 | #include "llvm/Support/Debug.h" |
| 32 | #include "llvm/Support/raw_ostream.h" |
| 33 | using namespace llvm; |
| 34 | using WebAssembly::SortRegion; |
| 35 | using WebAssembly::SortRegionInfo; |
| 36 | |
| 37 | #define DEBUG_TYPE "wasm-cfg-sort" |
| 38 | |
| 39 | // Option to disable EH pad first sorting. Only for testing unwind destination |
| 40 | // mismatches in CFGStackify. |
| 41 | static cl::opt<bool> WasmDisableEHPadSort( |
| 42 | "wasm-disable-ehpad-sort" , cl::ReallyHidden, |
| 43 | cl::desc( |
| 44 | "WebAssembly: Disable EH pad-first sort order. Testing purpose only." ), |
| 45 | cl::init(Val: false)); |
| 46 | |
| 47 | namespace { |
| 48 | |
| 49 | class WebAssemblyCFGSort final : public MachineFunctionPass { |
| 50 | StringRef getPassName() const override { return "WebAssembly CFG Sort" ; } |
| 51 | |
| 52 | void getAnalysisUsage(AnalysisUsage &AU) const override { |
| 53 | AU.setPreservesCFG(); |
| 54 | AU.addRequired<MachineDominatorTreeWrapperPass>(); |
| 55 | AU.addPreserved<MachineDominatorTreeWrapperPass>(); |
| 56 | AU.addRequired<MachineLoopInfoWrapperPass>(); |
| 57 | AU.addPreserved<MachineLoopInfoWrapperPass>(); |
| 58 | AU.addRequired<WebAssemblyExceptionInfo>(); |
| 59 | AU.addPreserved<WebAssemblyExceptionInfo>(); |
| 60 | MachineFunctionPass::getAnalysisUsage(AU); |
| 61 | } |
| 62 | |
| 63 | bool runOnMachineFunction(MachineFunction &MF) override; |
| 64 | |
| 65 | public: |
| 66 | static char ID; // Pass identification, replacement for typeid |
| 67 | WebAssemblyCFGSort() : MachineFunctionPass(ID) {} |
| 68 | }; |
| 69 | } // end anonymous namespace |
| 70 | |
| 71 | char WebAssemblyCFGSort::ID = 0; |
| 72 | INITIALIZE_PASS(WebAssemblyCFGSort, DEBUG_TYPE, |
| 73 | "Reorders blocks in topological order" , false, false) |
| 74 | |
| 75 | FunctionPass *llvm::createWebAssemblyCFGSort() { |
| 76 | return new WebAssemblyCFGSort(); |
| 77 | } |
| 78 | |
| 79 | static void maybeUpdateTerminator(MachineBasicBlock *MBB) { |
| 80 | #ifndef NDEBUG |
| 81 | bool AnyBarrier = false; |
| 82 | #endif |
| 83 | bool AllAnalyzable = true; |
| 84 | for (const MachineInstr &Term : MBB->terminators()) { |
| 85 | #ifndef NDEBUG |
| 86 | AnyBarrier |= Term.isBarrier(); |
| 87 | #endif |
| 88 | AllAnalyzable &= Term.isBranch() && !Term.isIndirectBranch(); |
| 89 | } |
| 90 | assert((AnyBarrier || AllAnalyzable) && |
| 91 | "analyzeBranch needs to analyze any block with a fallthrough" ); |
| 92 | |
| 93 | // Find the layout successor from the original block order. |
| 94 | MachineFunction *MF = MBB->getParent(); |
| 95 | MachineBasicBlock *OriginalSuccessor = |
| 96 | unsigned(MBB->getNumber() + 1) < MF->getNumBlockIDs() |
| 97 | ? MF->getBlockNumbered(N: MBB->getNumber() + 1) |
| 98 | : nullptr; |
| 99 | |
| 100 | if (AllAnalyzable) |
| 101 | MBB->updateTerminator(PreviousLayoutSuccessor: OriginalSuccessor); |
| 102 | } |
| 103 | |
| 104 | namespace { |
| 105 | // EH pads are selected first regardless of the block comparison order. |
| 106 | // When only one of the BBs is an EH pad, we give a higher priority to it, to |
| 107 | // prevent common mismatches between possibly throwing calls and ehpads they |
| 108 | // unwind to, as in the example below: |
| 109 | // |
| 110 | // bb0: |
| 111 | // call @foo // If this throws, unwind to bb2 |
| 112 | // bb1: |
| 113 | // call @bar // If this throws, unwind to bb3 |
| 114 | // bb2 (ehpad): |
| 115 | // handler_bb2 |
| 116 | // bb3 (ehpad): |
| 117 | // handler_bb3 |
| 118 | // continuing code |
| 119 | // |
| 120 | // Because this pass tries to preserve the original BB order, this order will |
| 121 | // not change. But this will result in this try-catch structure in CFGStackify, |
| 122 | // resulting in a mismatch: |
| 123 | // try |
| 124 | // try |
| 125 | // call @foo |
| 126 | // call @bar // This should unwind to bb3, not bb2! |
| 127 | // catch |
| 128 | // handler_bb2 |
| 129 | // end |
| 130 | // catch |
| 131 | // handler_bb3 |
| 132 | // end |
| 133 | // continuing code |
| 134 | // |
| 135 | // If we give a higher priority to an EH pad whenever it is ready in this |
| 136 | // example, when both bb1 and bb2 are ready, we would pick up bb2 first. |
| 137 | |
| 138 | /// Sort blocks by their number. |
| 139 | struct CompareBlockNumbers { |
| 140 | bool operator()(const MachineBasicBlock *A, |
| 141 | const MachineBasicBlock *B) const { |
| 142 | if (!WasmDisableEHPadSort) { |
| 143 | if (A->isEHPad() && !B->isEHPad()) |
| 144 | return false; |
| 145 | if (!A->isEHPad() && B->isEHPad()) |
| 146 | return true; |
| 147 | } |
| 148 | |
| 149 | return A->getNumber() > B->getNumber(); |
| 150 | } |
| 151 | }; |
| 152 | /// Sort blocks by their number in the opposite order.. |
| 153 | struct CompareBlockNumbersBackwards { |
| 154 | bool operator()(const MachineBasicBlock *A, |
| 155 | const MachineBasicBlock *B) const { |
| 156 | if (!WasmDisableEHPadSort) { |
| 157 | if (A->isEHPad() && !B->isEHPad()) |
| 158 | return false; |
| 159 | if (!A->isEHPad() && B->isEHPad()) |
| 160 | return true; |
| 161 | } |
| 162 | |
| 163 | return A->getNumber() < B->getNumber(); |
| 164 | } |
| 165 | }; |
| 166 | /// Bookkeeping for a region to help ensure that we don't mix blocks not |
| 167 | /// dominated by the its header among its blocks. |
| 168 | struct Entry { |
| 169 | const SortRegion *TheRegion; |
| 170 | unsigned NumBlocksLeft; |
| 171 | |
| 172 | /// List of blocks not dominated by Loop's header that are deferred until |
| 173 | /// after all of Loop's blocks have been seen. |
| 174 | std::vector<MachineBasicBlock *> Deferred; |
| 175 | |
| 176 | explicit Entry(const SortRegion *R) |
| 177 | : TheRegion(R), NumBlocksLeft(R->getNumBlocks()) {} |
| 178 | }; |
| 179 | } // end anonymous namespace |
| 180 | |
| 181 | /// Sort the blocks, taking special care to make sure that regions are not |
| 182 | /// interrupted by blocks not dominated by their header. |
| 183 | /// TODO: There are many opportunities for improving the heuristics here. |
| 184 | /// Explore them. |
| 185 | static void sortBlocks(MachineFunction &MF, const MachineLoopInfo &MLI, |
| 186 | const WebAssemblyExceptionInfo &WEI, |
| 187 | MachineDominatorTree &MDT) { |
| 188 | // Remember original layout ordering, so we can update terminators after |
| 189 | // reordering to point to the original layout successor. |
| 190 | MF.RenumberBlocks(); |
| 191 | MDT.updateBlockNumbers(); |
| 192 | |
| 193 | // Prepare for a topological sort: Record the number of predecessors each |
| 194 | // block has, ignoring loop backedges. |
| 195 | SmallVector<unsigned, 16> NumPredsLeft(MF.getNumBlockIDs(), 0); |
| 196 | for (MachineBasicBlock &MBB : MF) { |
| 197 | unsigned N = MBB.pred_size(); |
| 198 | if (MachineLoop *L = MLI.getLoopFor(BB: &MBB)) |
| 199 | if (L->getHeader() == &MBB) |
| 200 | for (const MachineBasicBlock *Pred : MBB.predecessors()) |
| 201 | if (L->contains(BB: Pred)) |
| 202 | --N; |
| 203 | NumPredsLeft[MBB.getNumber()] = N; |
| 204 | } |
| 205 | |
| 206 | // Topological sort the CFG, with additional constraints: |
| 207 | // - Between a region header and the last block in the region, there can be |
| 208 | // no blocks not dominated by its header. |
| 209 | // - It's desirable to preserve the original block order when possible. |
| 210 | // We use two ready lists; Preferred and Ready. Preferred has recently |
| 211 | // processed successors, to help preserve block sequences from the original |
| 212 | // order. Ready has the remaining ready blocks. EH blocks are picked first |
| 213 | // from both queues. |
| 214 | PriorityQueue<MachineBasicBlock *, std::vector<MachineBasicBlock *>, |
| 215 | CompareBlockNumbers> |
| 216 | Preferred; |
| 217 | PriorityQueue<MachineBasicBlock *, std::vector<MachineBasicBlock *>, |
| 218 | CompareBlockNumbersBackwards> |
| 219 | Ready; |
| 220 | |
| 221 | const auto *EHInfo = MF.getWasmEHFuncInfo(); |
| 222 | SortRegionInfo SRI(MLI, WEI); |
| 223 | SmallVector<Entry, 4> Entries; |
| 224 | for (MachineBasicBlock *MBB = &MF.front();;) { |
| 225 | const SortRegion *R = SRI.getRegionFor(MBB); |
| 226 | if (R) { |
| 227 | // If MBB is a region header, add it to the active region list. We can't |
| 228 | // put any blocks that it doesn't dominate until we see the end of the |
| 229 | // region. |
| 230 | if (R->getHeader() == MBB) |
| 231 | Entries.push_back(Elt: Entry(R)); |
| 232 | // For each active region the block is in, decrement the count. If MBB is |
| 233 | // the last block in an active region, take it off the list and pick up |
| 234 | // any blocks deferred because the header didn't dominate them. |
| 235 | for (Entry &E : Entries) |
| 236 | if (E.TheRegion->contains(MBB) && --E.NumBlocksLeft == 0) |
| 237 | for (auto *DeferredBlock : E.Deferred) |
| 238 | Ready.push(x: DeferredBlock); |
| 239 | while (!Entries.empty() && Entries.back().NumBlocksLeft == 0) |
| 240 | Entries.pop_back(); |
| 241 | } |
| 242 | // The main topological sort logic. |
| 243 | for (MachineBasicBlock *Succ : MBB->successors()) { |
| 244 | // Ignore backedges. |
| 245 | if (MachineLoop *SuccL = MLI.getLoopFor(BB: Succ)) |
| 246 | if (SuccL->getHeader() == Succ && SuccL->contains(BB: MBB)) |
| 247 | continue; |
| 248 | // Decrement the predecessor count. If it's now zero, it's ready. |
| 249 | if (--NumPredsLeft[Succ->getNumber()] == 0) { |
| 250 | // When we are in a SortRegion, we allow sorting of not only BBs that |
| 251 | // belong to the current (innermost) region but also BBs that are |
| 252 | // dominated by the current region header. But we should not do this for |
| 253 | // exceptions because there can be cases in which, for example: |
| 254 | // EHPad A's unwind destination (where the exception lands when it is |
| 255 | // not caught by EHPad A) is EHPad B, so EHPad B does not belong to the |
| 256 | // exception dominated by EHPad A. But EHPad B is dominated by EHPad A, |
| 257 | // so EHPad B can be sorted within EHPad A's exception. This is |
| 258 | // incorrect because we may end up delegating/rethrowing to an inner |
| 259 | // scope in CFGStackify. So here we make sure those unwind destinations |
| 260 | // are deferred until their unwind source's exception is sorted. |
| 261 | if (EHInfo && EHInfo->hasUnwindSrcs(MBB: Succ)) { |
| 262 | SmallPtrSet<MachineBasicBlock *, 4> UnwindSrcs = |
| 263 | EHInfo->getUnwindSrcs(MBB: Succ); |
| 264 | bool IsDeferred = false; |
| 265 | for (Entry &E : Entries) { |
| 266 | if (UnwindSrcs.count(Ptr: E.TheRegion->getHeader())) { |
| 267 | E.Deferred.push_back(x: Succ); |
| 268 | IsDeferred = true; |
| 269 | break; |
| 270 | } |
| 271 | } |
| 272 | if (IsDeferred) |
| 273 | continue; |
| 274 | } |
| 275 | Preferred.push(x: Succ); |
| 276 | } |
| 277 | } |
| 278 | // Determine the block to follow MBB. First try to find a preferred block, |
| 279 | // to preserve the original block order when possible. |
| 280 | MachineBasicBlock *Next = nullptr; |
| 281 | while (!Preferred.empty()) { |
| 282 | Next = Preferred.top(); |
| 283 | Preferred.pop(); |
| 284 | // If X isn't dominated by the top active region header, defer it until |
| 285 | // that region is done. |
| 286 | if (!Entries.empty() && |
| 287 | !MDT.dominates(A: Entries.back().TheRegion->getHeader(), B: Next)) { |
| 288 | Entries.back().Deferred.push_back(x: Next); |
| 289 | Next = nullptr; |
| 290 | continue; |
| 291 | } |
| 292 | // If Next was originally ordered before MBB, and it isn't because it was |
| 293 | // loop-rotated above the header, it's not preferred. |
| 294 | if (Next->getNumber() < MBB->getNumber() && |
| 295 | (WasmDisableEHPadSort || !Next->isEHPad()) && |
| 296 | (!R || !R->contains(MBB: Next) || |
| 297 | R->getHeader()->getNumber() < Next->getNumber())) { |
| 298 | Ready.push(x: Next); |
| 299 | Next = nullptr; |
| 300 | continue; |
| 301 | } |
| 302 | break; |
| 303 | } |
| 304 | // If we didn't find a suitable block in the Preferred list, check the |
| 305 | // general Ready list. |
| 306 | if (!Next) { |
| 307 | // If there are no more blocks to process, we're done. |
| 308 | if (Ready.empty()) { |
| 309 | maybeUpdateTerminator(MBB); |
| 310 | break; |
| 311 | } |
| 312 | for (;;) { |
| 313 | Next = Ready.top(); |
| 314 | Ready.pop(); |
| 315 | // If Next isn't dominated by the top active region header, defer it |
| 316 | // until that region is done. |
| 317 | if (!Entries.empty() && |
| 318 | !MDT.dominates(A: Entries.back().TheRegion->getHeader(), B: Next)) { |
| 319 | Entries.back().Deferred.push_back(x: Next); |
| 320 | continue; |
| 321 | } |
| 322 | break; |
| 323 | } |
| 324 | } |
| 325 | // Move the next block into place and iterate. |
| 326 | Next->moveAfter(NewBefore: MBB); |
| 327 | maybeUpdateTerminator(MBB); |
| 328 | MBB = Next; |
| 329 | } |
| 330 | assert(Entries.empty() && "Active sort region list not finished" ); |
| 331 | MF.RenumberBlocks(); |
| 332 | MDT.updateBlockNumbers(); |
| 333 | |
| 334 | #ifndef NDEBUG |
| 335 | SmallSetVector<const SortRegion *, 8> OnStack; |
| 336 | |
| 337 | // Insert a sentinel representing the degenerate loop that starts at the |
| 338 | // function entry block and includes the entire function as a "loop" that |
| 339 | // executes once. |
| 340 | OnStack.insert(nullptr); |
| 341 | |
| 342 | for (auto &MBB : MF) { |
| 343 | assert(MBB.getNumber() >= 0 && "Renumbered blocks should be non-negative." ); |
| 344 | const SortRegion *Region = SRI.getRegionFor(&MBB); |
| 345 | |
| 346 | if (Region && &MBB == Region->getHeader()) { |
| 347 | // Region header. |
| 348 | if (Region->isLoop()) { |
| 349 | // Loop header. The loop predecessor should be sorted above, and the |
| 350 | // other predecessors should be backedges below. |
| 351 | for (auto *Pred : MBB.predecessors()) |
| 352 | assert( |
| 353 | (Pred->getNumber() < MBB.getNumber() || Region->contains(Pred)) && |
| 354 | "Loop header predecessors must be loop predecessors or " |
| 355 | "backedges" ); |
| 356 | } else { |
| 357 | // Exception header. All predecessors should be sorted above. |
| 358 | for (auto *Pred : MBB.predecessors()) |
| 359 | assert(Pred->getNumber() < MBB.getNumber() && |
| 360 | "Non-loop-header predecessors should be topologically sorted" ); |
| 361 | } |
| 362 | assert(OnStack.insert(Region) && |
| 363 | "Regions should be declared at most once." ); |
| 364 | |
| 365 | } else { |
| 366 | // Not a region header. All predecessors should be sorted above. |
| 367 | for (auto *Pred : MBB.predecessors()) |
| 368 | assert(Pred->getNumber() < MBB.getNumber() && |
| 369 | "Non-loop-header predecessors should be topologically sorted" ); |
| 370 | assert(OnStack.count(SRI.getRegionFor(&MBB)) && |
| 371 | "Blocks must be nested in their regions" ); |
| 372 | } |
| 373 | while (OnStack.size() > 1 && &MBB == SRI.getBottom(OnStack.back())) |
| 374 | OnStack.pop_back(); |
| 375 | } |
| 376 | assert(OnStack.pop_back_val() == nullptr && |
| 377 | "The function entry block shouldn't actually be a region header" ); |
| 378 | assert(OnStack.empty() && |
| 379 | "Control flow stack pushes and pops should be balanced." ); |
| 380 | #endif |
| 381 | } |
| 382 | |
| 383 | bool WebAssemblyCFGSort::runOnMachineFunction(MachineFunction &MF) { |
| 384 | LLVM_DEBUG(dbgs() << "********** CFG Sorting **********\n" |
| 385 | "********** Function: " |
| 386 | << MF.getName() << '\n'); |
| 387 | |
| 388 | const auto &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI(); |
| 389 | const auto &WEI = getAnalysis<WebAssemblyExceptionInfo>(); |
| 390 | auto &MDT = getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree(); |
| 391 | // Liveness is not tracked for VALUE_STACK physreg. |
| 392 | MF.getRegInfo().invalidateLiveness(); |
| 393 | |
| 394 | // Sort the blocks, with contiguous sort regions. |
| 395 | sortBlocks(MF, MLI, WEI, MDT); |
| 396 | |
| 397 | return true; |
| 398 | } |
| 399 | |