| 1 | //===--- WebAssemblyExceptionInfo.cpp - Exception Infomation --------------===// |
| 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 | /// \brief This file implements WebAssemblyException information analysis. |
| 11 | /// |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "WebAssemblyExceptionInfo.h" |
| 15 | #include "WebAssemblyUtilities.h" |
| 16 | #include "llvm/ADT/DepthFirstIterator.h" |
| 17 | #include "llvm/ADT/PostOrderIterator.h" |
| 18 | #include "llvm/CodeGen/MachineDominanceFrontier.h" |
| 19 | #include "llvm/CodeGen/MachineDominators.h" |
| 20 | #include "llvm/CodeGen/WasmEHFuncInfo.h" |
| 21 | #include "llvm/IR/Function.h" |
| 22 | #include "llvm/InitializePasses.h" |
| 23 | #include "llvm/MC/MCAsmInfo.h" |
| 24 | #include "llvm/Target/TargetMachine.h" |
| 25 | |
| 26 | using namespace llvm; |
| 27 | |
| 28 | #define DEBUG_TYPE "wasm-exception-info" |
| 29 | |
| 30 | char WebAssemblyExceptionInfo::ID = 0; |
| 31 | |
| 32 | INITIALIZE_PASS_BEGIN(WebAssemblyExceptionInfo, DEBUG_TYPE, |
| 33 | "WebAssembly Exception Information" , true, true) |
| 34 | INITIALIZE_PASS_DEPENDENCY(MachineDominatorTreeWrapperPass) |
| 35 | INITIALIZE_PASS_DEPENDENCY(MachineDominanceFrontier) |
| 36 | INITIALIZE_PASS_END(WebAssemblyExceptionInfo, DEBUG_TYPE, |
| 37 | "WebAssembly Exception Information" , true, true) |
| 38 | |
| 39 | bool WebAssemblyExceptionInfo::runOnMachineFunction(MachineFunction &MF) { |
| 40 | LLVM_DEBUG(dbgs() << "********** Exception Info Calculation **********\n" |
| 41 | "********** Function: " |
| 42 | << MF.getName() << '\n'); |
| 43 | releaseMemory(); |
| 44 | if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() != |
| 45 | ExceptionHandling::Wasm || |
| 46 | !MF.getFunction().hasPersonalityFn()) |
| 47 | return false; |
| 48 | auto &MDT = getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree(); |
| 49 | auto &MDF = getAnalysis<MachineDominanceFrontier>(); |
| 50 | recalculate(MF, MDT, MDF); |
| 51 | LLVM_DEBUG(dump()); |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | // Check if Dst is reachable from Src using BFS. Search only within BBs |
| 56 | // dominated by Header. |
| 57 | static bool isReachableAmongDominated(const MachineBasicBlock *Src, |
| 58 | const MachineBasicBlock *Dst, |
| 59 | const MachineBasicBlock *, |
| 60 | const MachineDominatorTree &MDT) { |
| 61 | assert(MDT.dominates(Header, Dst)); |
| 62 | SmallVector<const MachineBasicBlock *, 8> WL; |
| 63 | SmallPtrSet<const MachineBasicBlock *, 8> Visited; |
| 64 | WL.push_back(Elt: Src); |
| 65 | |
| 66 | while (!WL.empty()) { |
| 67 | const auto *MBB = WL.pop_back_val(); |
| 68 | if (MBB == Dst) |
| 69 | return true; |
| 70 | Visited.insert(Ptr: MBB); |
| 71 | for (auto *Succ : MBB->successors()) |
| 72 | if (!Visited.count(Ptr: Succ) && MDT.dominates(A: Header, B: Succ)) |
| 73 | WL.push_back(Elt: Succ); |
| 74 | } |
| 75 | return false; |
| 76 | } |
| 77 | |
| 78 | void WebAssemblyExceptionInfo::recalculate( |
| 79 | MachineFunction &MF, MachineDominatorTree &MDT, |
| 80 | const MachineDominanceFrontier &MDF) { |
| 81 | // Postorder traversal of the dominator tree. |
| 82 | SmallVector<std::unique_ptr<WebAssemblyException>, 8> Exceptions; |
| 83 | for (auto *DomNode : post_order(G: &MDT)) { |
| 84 | MachineBasicBlock *EHPad = DomNode->getBlock(); |
| 85 | if (!EHPad->isEHPad()) |
| 86 | continue; |
| 87 | auto WE = std::make_unique<WebAssemblyException>(args&: EHPad); |
| 88 | discoverAndMapException(WE: WE.get(), MDT, MDF); |
| 89 | Exceptions.push_back(Elt: std::move(WE)); |
| 90 | } |
| 91 | |
| 92 | // WasmEHFuncInfo contains a map of <catchpad, its next unwind destination>, |
| 93 | // which means, if an exception is not caught by the catchpad, it should end |
| 94 | // up in the next unwind destination stored in this data structure. (It is |
| 95 | // written as catchswitch's 'unwind' destination in ll files.) The below is an |
| 96 | // intuitive example of their relationship in C++ code: |
| 97 | // try { |
| 98 | // try { |
| 99 | // } catch (int) { // catchpad |
| 100 | // ... // this catch (int) { ... } is grouped as an exception |
| 101 | // } |
| 102 | // } catch (...) { // next unwind destination |
| 103 | // } |
| 104 | // (The example is try-catches for illustration purpose, but the unwind |
| 105 | // destination can be also a cleanuppad generated by destructor calls.) So the |
| 106 | // unwind destination is in the outside of the catchpad's exception. |
| 107 | // |
| 108 | // We group exceptions in this analysis simply by including all BBs dominated |
| 109 | // by an EH pad. But in case the EH pad's unwind destination does not have any |
| 110 | // children outside of the exception, that unwind destination ends up also |
| 111 | // being dominated by the EH pad and included in the exception, which is not |
| 112 | // semantically correct, because it unwinds/rethrows into an inner scope. |
| 113 | // |
| 114 | // Here we extract those unwind destinations from their (incorrect) parent |
| 115 | // exception. Note that the unwind destinations may not be an immediate |
| 116 | // children of the parent exception, so we have to traverse the parent chain. |
| 117 | // |
| 118 | // We should traverse BBs in the preorder of the dominator tree, because |
| 119 | // otherwise the result can be incorrect. For example, when there are three |
| 120 | // exceptions A, B, and C and A > B > C (> is subexception relationship here), |
| 121 | // and A's unwind destination is B and B's is C. When we visit B before A, we |
| 122 | // end up extracting C only out of B but not out of A. |
| 123 | const auto *EHInfo = MF.getWasmEHFuncInfo(); |
| 124 | assert(EHInfo); |
| 125 | SmallVector<std::pair<WebAssemblyException *, WebAssemblyException *>> |
| 126 | UnwindWEVec; |
| 127 | for (auto *DomNode : depth_first(G: &MDT)) { |
| 128 | MachineBasicBlock *EHPad = DomNode->getBlock(); |
| 129 | if (!EHPad->isEHPad()) |
| 130 | continue; |
| 131 | if (!EHInfo->hasUnwindDest(MBB: EHPad)) |
| 132 | continue; |
| 133 | auto *UnwindDest = EHInfo->getUnwindDest(MBB: EHPad); |
| 134 | auto *SrcWE = getExceptionFor(MBB: EHPad); |
| 135 | auto *DstWE = getExceptionFor(MBB: UnwindDest); |
| 136 | if (SrcWE->contains(WE: DstWE)) { |
| 137 | UnwindWEVec.push_back(Elt: std::make_pair(x&: SrcWE, y&: DstWE)); |
| 138 | LLVM_DEBUG(dbgs() << "Unwind destination ExceptionInfo fix:\n " |
| 139 | << DstWE->getEHPad()->getNumber() << "." |
| 140 | << DstWE->getEHPad()->getName() |
| 141 | << "'s exception is taken out of " |
| 142 | << SrcWE->getEHPad()->getNumber() << "." |
| 143 | << SrcWE->getEHPad()->getName() << "'s exception\n" ); |
| 144 | DstWE->setParentException(SrcWE->getParentException()); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // After fixing subexception relationship between unwind destinations above, |
| 149 | // there can still be remaining discrepancies. |
| 150 | // |
| 151 | // For example, suppose Exception A is dominated by EHPad A and Exception B is |
| 152 | // dominated by EHPad B. EHPad A's unwind destination is EHPad B, but because |
| 153 | // EHPad B is dominated by EHPad A, the initial grouping makes Exception B a |
| 154 | // subexception of Exception A, and we fix it by taking Exception B out of |
| 155 | // Exception A above. But there can still be remaining BBs within Exception A |
| 156 | // that are reachable from Exception B. These BBs semantically don't belong |
| 157 | // to Exception A and were not a part of this 'catch' clause or cleanup code |
| 158 | // in the original code, but they just happened to be grouped within Exception |
| 159 | // A because they were dominated by EHPad A. We fix this case by taking those |
| 160 | // BBs out of the incorrect exception and all its subexceptions that it |
| 161 | // belongs to. |
| 162 | // |
| 163 | // 1. First, we take out remaining incorrect subexceptions. This part is |
| 164 | // easier, because we haven't added BBs to exceptions yet, we only need to |
| 165 | // change parent exception pointer. |
| 166 | for (auto *DomNode : depth_first(G: &MDT)) { |
| 167 | MachineBasicBlock *EHPad = DomNode->getBlock(); |
| 168 | if (!EHPad->isEHPad()) |
| 169 | continue; |
| 170 | auto *WE = getExceptionFor(MBB: EHPad); |
| 171 | |
| 172 | // For each source EHPad -> unwind destination EHPad |
| 173 | for (auto &P : UnwindWEVec) { |
| 174 | auto *SrcWE = P.first; |
| 175 | auto *DstWE = P.second; |
| 176 | // If WE (the current EH pad's exception) is still contained in SrcWE but |
| 177 | // reachable from DstWE that was taken out of SrcWE above, we have to take |
| 178 | // out WE out of SrcWE too. |
| 179 | if (WE != SrcWE && SrcWE->contains(WE) && !DstWE->contains(WE) && |
| 180 | isReachableAmongDominated(Src: DstWE->getEHPad(), Dst: EHPad, Header: SrcWE->getEHPad(), |
| 181 | MDT)) { |
| 182 | LLVM_DEBUG(dbgs() << "Remaining reachable ExceptionInfo fix:\n " |
| 183 | << WE->getEHPad()->getNumber() << "." |
| 184 | << WE->getEHPad()->getName() |
| 185 | << "'s exception is taken out of " |
| 186 | << SrcWE->getEHPad()->getNumber() << "." |
| 187 | << SrcWE->getEHPad()->getName() << "'s exception\n" ); |
| 188 | WE->setParentException(SrcWE->getParentException()); |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // Add BBs to exceptions' block set. This is a preparation to take out |
| 194 | // remaining incorect BBs from exceptions, because we need to iterate over BBs |
| 195 | // for each exception. |
| 196 | for (auto *DomNode : post_order(G: &MDT)) { |
| 197 | MachineBasicBlock *MBB = DomNode->getBlock(); |
| 198 | WebAssemblyException *WE = getExceptionFor(MBB); |
| 199 | for (; WE; WE = WE->getParentException()) |
| 200 | WE->addToBlocksSet(MBB); |
| 201 | } |
| 202 | |
| 203 | // 2. We take out remaining individual BBs out. Now we have added BBs to each |
| 204 | // exceptions' BlockSet, when we take a BB out of an exception, we need to fix |
| 205 | // those sets too. |
| 206 | for (auto &P : UnwindWEVec) { |
| 207 | auto *SrcWE = P.first; |
| 208 | auto *DstWE = P.second; |
| 209 | |
| 210 | SrcWE->getBlocksSet().remove_if(P: [&](MachineBasicBlock *MBB){ |
| 211 | if (MBB->isEHPad()) { |
| 212 | assert(!isReachableAmongDominated(DstWE->getEHPad(), MBB, |
| 213 | SrcWE->getEHPad(), MDT) && |
| 214 | "We already handled EH pads above" ); |
| 215 | return false; |
| 216 | } |
| 217 | if (isReachableAmongDominated(Src: DstWE->getEHPad(), Dst: MBB, Header: SrcWE->getEHPad(), |
| 218 | MDT)) { |
| 219 | LLVM_DEBUG(dbgs() << "Remainder BB: " << MBB->getNumber() << "." |
| 220 | << MBB->getName() << " is\n" ); |
| 221 | WebAssemblyException *InnerWE = getExceptionFor(MBB); |
| 222 | while (InnerWE != SrcWE) { |
| 223 | LLVM_DEBUG(dbgs() |
| 224 | << " removed from " << InnerWE->getEHPad()->getNumber() |
| 225 | << "." << InnerWE->getEHPad()->getName() |
| 226 | << "'s exception\n" ); |
| 227 | InnerWE->removeFromBlocksSet(MBB); |
| 228 | InnerWE = InnerWE->getParentException(); |
| 229 | } |
| 230 | LLVM_DEBUG(dbgs() << " removed from " << SrcWE->getEHPad()->getNumber() |
| 231 | << "." << SrcWE->getEHPad()->getName() |
| 232 | << "'s exception\n" ); |
| 233 | changeExceptionFor(MBB, WE: SrcWE->getParentException()); |
| 234 | if (SrcWE->getParentException()) |
| 235 | SrcWE->getParentException()->addToBlocksSet(MBB); |
| 236 | return true; |
| 237 | } |
| 238 | return false; |
| 239 | }); |
| 240 | } |
| 241 | |
| 242 | // Add BBs to exceptions' block vector |
| 243 | for (auto *DomNode : post_order(G: &MDT)) { |
| 244 | MachineBasicBlock *MBB = DomNode->getBlock(); |
| 245 | WebAssemblyException *WE = getExceptionFor(MBB); |
| 246 | for (; WE; WE = WE->getParentException()) |
| 247 | WE->addToBlocksVector(MBB); |
| 248 | } |
| 249 | |
| 250 | SmallVector<WebAssemblyException*, 8> ExceptionPointers; |
| 251 | ExceptionPointers.reserve(N: Exceptions.size()); |
| 252 | |
| 253 | // Add subexceptions to exceptions |
| 254 | for (auto &WE : Exceptions) { |
| 255 | ExceptionPointers.push_back(Elt: WE.get()); |
| 256 | if (WE->getParentException()) |
| 257 | WE->getParentException()->getSubExceptions().push_back(x: std::move(WE)); |
| 258 | else |
| 259 | addTopLevelException(WE: std::move(WE)); |
| 260 | } |
| 261 | |
| 262 | // For convenience, Blocks and SubExceptions are inserted in postorder. |
| 263 | // Reverse the lists. |
| 264 | for (auto *WE : ExceptionPointers) { |
| 265 | WE->reverseBlock(); |
| 266 | std::reverse(first: WE->getSubExceptions().begin(), last: WE->getSubExceptions().end()); |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | void WebAssemblyExceptionInfo::releaseMemory() { |
| 271 | BBMap.clear(); |
| 272 | TopLevelExceptions.clear(); |
| 273 | } |
| 274 | |
| 275 | void WebAssemblyExceptionInfo::getAnalysisUsage(AnalysisUsage &AU) const { |
| 276 | AU.setPreservesAll(); |
| 277 | AU.addRequired<MachineDominatorTreeWrapperPass>(); |
| 278 | AU.addRequired<MachineDominanceFrontier>(); |
| 279 | MachineFunctionPass::getAnalysisUsage(AU); |
| 280 | } |
| 281 | |
| 282 | void WebAssemblyExceptionInfo::discoverAndMapException( |
| 283 | WebAssemblyException *WE, const MachineDominatorTree &MDT, |
| 284 | const MachineDominanceFrontier &MDF) { |
| 285 | unsigned NumBlocks = 0; |
| 286 | unsigned NumSubExceptions = 0; |
| 287 | |
| 288 | // Map blocks that belong to a catchpad / cleanuppad |
| 289 | MachineBasicBlock *EHPad = WE->getEHPad(); |
| 290 | SmallVector<MachineBasicBlock *, 8> WL; |
| 291 | WL.push_back(Elt: EHPad); |
| 292 | while (!WL.empty()) { |
| 293 | MachineBasicBlock *MBB = WL.pop_back_val(); |
| 294 | |
| 295 | // Find its outermost discovered exception. If this is a discovered block, |
| 296 | // check if it is already discovered to be a subexception of this exception. |
| 297 | WebAssemblyException *SubE = getOutermostException(MBB); |
| 298 | if (SubE) { |
| 299 | if (SubE != WE) { |
| 300 | // Discover a subexception of this exception. |
| 301 | SubE->setParentException(WE); |
| 302 | ++NumSubExceptions; |
| 303 | NumBlocks += SubE->getBlocksVector().capacity(); |
| 304 | // All blocks that belong to this subexception have been already |
| 305 | // discovered. Skip all of them. Add the subexception's landing pad's |
| 306 | // dominance frontier to the worklist. |
| 307 | for (auto &Frontier : MDF.find(B: SubE->getEHPad())->second) |
| 308 | if (MDT.dominates(A: EHPad, B: Frontier)) |
| 309 | WL.push_back(Elt: Frontier); |
| 310 | } |
| 311 | continue; |
| 312 | } |
| 313 | |
| 314 | // This is an undiscovered block. Map it to the current exception. |
| 315 | changeExceptionFor(MBB, WE); |
| 316 | ++NumBlocks; |
| 317 | |
| 318 | // Add successors dominated by the current BB to the worklist. |
| 319 | for (auto *Succ : MBB->successors()) |
| 320 | if (MDT.dominates(A: EHPad, B: Succ)) |
| 321 | WL.push_back(Elt: Succ); |
| 322 | } |
| 323 | |
| 324 | WE->getSubExceptions().reserve(n: NumSubExceptions); |
| 325 | WE->reserveBlocks(Size: NumBlocks); |
| 326 | } |
| 327 | |
| 328 | WebAssemblyException * |
| 329 | WebAssemblyExceptionInfo::getOutermostException(MachineBasicBlock *MBB) const { |
| 330 | WebAssemblyException *WE = getExceptionFor(MBB); |
| 331 | if (WE) { |
| 332 | while (WebAssemblyException *Parent = WE->getParentException()) |
| 333 | WE = Parent; |
| 334 | } |
| 335 | return WE; |
| 336 | } |
| 337 | |
| 338 | void WebAssemblyException::print(raw_ostream &OS, unsigned Depth) const { |
| 339 | OS.indent(NumSpaces: Depth * 2) << "Exception at depth " << getExceptionDepth() |
| 340 | << " containing: " ; |
| 341 | |
| 342 | for (unsigned I = 0; I < getBlocks().size(); ++I) { |
| 343 | MachineBasicBlock *MBB = getBlocks()[I]; |
| 344 | if (I) |
| 345 | OS << ", " ; |
| 346 | OS << "%bb." << MBB->getNumber(); |
| 347 | if (const auto *BB = MBB->getBasicBlock()) |
| 348 | if (BB->hasName()) |
| 349 | OS << "." << BB->getName(); |
| 350 | |
| 351 | if (getEHPad() == MBB) |
| 352 | OS << " (landing-pad)" ; |
| 353 | } |
| 354 | OS << "\n" ; |
| 355 | |
| 356 | for (auto &SubE : SubExceptions) |
| 357 | SubE->print(OS, Depth: Depth + 2); |
| 358 | } |
| 359 | |
| 360 | #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) |
| 361 | LLVM_DUMP_METHOD void WebAssemblyException::dump() const { print(dbgs()); } |
| 362 | #endif |
| 363 | |
| 364 | raw_ostream &operator<<(raw_ostream &OS, const WebAssemblyException &WE) { |
| 365 | WE.print(OS); |
| 366 | return OS; |
| 367 | } |
| 368 | |
| 369 | void WebAssemblyExceptionInfo::print(raw_ostream &OS, const Module *) const { |
| 370 | for (auto &WE : TopLevelExceptions) |
| 371 | WE->print(OS); |
| 372 | } |
| 373 | |