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