1 //=== WebAssemblyLateEHPrepare.cpp - WebAssembly Exception Preparation -===// 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 Does various transformations for exception handling. 11 /// 12 //===----------------------------------------------------------------------===// 13 14 #include "MCTargetDesc/WebAssemblyMCTargetDesc.h" 15 #include "WebAssembly.h" 16 #include "WebAssemblySubtarget.h" 17 #include "WebAssemblyUtilities.h" 18 #include "llvm/ADT/SmallPtrSet.h" 19 #include "llvm/CodeGen/MachineFunctionPass.h" 20 #include "llvm/CodeGen/MachineInstrBuilder.h" 21 #include "llvm/CodeGen/WasmEHFuncInfo.h" 22 #include "llvm/MC/MCAsmInfo.h" 23 #include "llvm/Support/Debug.h" 24 #include "llvm/Target/TargetMachine.h" 25 using namespace llvm; 26 27 #define DEBUG_TYPE "wasm-late-eh-prepare" 28 29 namespace { 30 class WebAssemblyLateEHPrepare final : public MachineFunctionPass { 31 StringRef getPassName() const override { 32 return "WebAssembly Late Prepare Exception"; 33 } 34 35 bool runOnMachineFunction(MachineFunction &MF) override; 36 bool removeUnreachableEHPads(MachineFunction &MF); 37 void recordCatchRetBBs(MachineFunction &MF); 38 bool hoistCatches(MachineFunction &MF); 39 bool addCatchAlls(MachineFunction &MF); 40 bool replaceFuncletReturns(MachineFunction &MF); 41 bool removeUnnecessaryUnreachables(MachineFunction &MF); 42 bool restoreStackPointer(MachineFunction &MF); 43 44 MachineBasicBlock *getMatchingEHPad(MachineInstr *MI); 45 SmallPtrSet<MachineBasicBlock *, 8> CatchRetBBs; 46 47 public: 48 static char ID; // Pass identification, replacement for typeid 49 WebAssemblyLateEHPrepare() : MachineFunctionPass(ID) {} 50 }; 51 } // end anonymous namespace 52 53 char WebAssemblyLateEHPrepare::ID = 0; 54 INITIALIZE_PASS(WebAssemblyLateEHPrepare, DEBUG_TYPE, 55 "WebAssembly Late Exception Preparation", false, false) 56 57 FunctionPass *llvm::createWebAssemblyLateEHPrepare() { 58 return new WebAssemblyLateEHPrepare(); 59 } 60 61 // Returns the nearest EH pad that dominates this instruction. This does not use 62 // dominator analysis; it just does BFS on its predecessors until arriving at an 63 // EH pad. This assumes valid EH scopes so the first EH pad it arrives in all 64 // possible search paths should be the same. 65 // Returns nullptr in case it does not find any EH pad in the search, or finds 66 // multiple different EH pads. 67 MachineBasicBlock * 68 WebAssemblyLateEHPrepare::getMatchingEHPad(MachineInstr *MI) { 69 MachineFunction *MF = MI->getParent()->getParent(); 70 SmallVector<MachineBasicBlock *, 2> WL; 71 SmallPtrSet<MachineBasicBlock *, 2> Visited; 72 WL.push_back(MI->getParent()); 73 MachineBasicBlock *EHPad = nullptr; 74 while (!WL.empty()) { 75 MachineBasicBlock *MBB = WL.pop_back_val(); 76 if (!Visited.insert(MBB).second) 77 continue; 78 if (MBB->isEHPad()) { 79 if (EHPad && EHPad != MBB) 80 return nullptr; 81 EHPad = MBB; 82 continue; 83 } 84 if (MBB == &MF->front()) 85 return nullptr; 86 for (auto *Pred : MBB->predecessors()) 87 if (!CatchRetBBs.count(Pred)) // We don't go into child scopes 88 WL.push_back(Pred); 89 } 90 return EHPad; 91 } 92 93 // Erase the specified BBs if the BB does not have any remaining predecessors, 94 // and also all its dead children. 95 template <typename Container> 96 static void eraseDeadBBsAndChildren(const Container &MBBs) { 97 SmallVector<MachineBasicBlock *, 8> WL(MBBs.begin(), MBBs.end()); 98 SmallPtrSet<MachineBasicBlock *, 8> Deleted; 99 while (!WL.empty()) { 100 MachineBasicBlock *MBB = WL.pop_back_val(); 101 if (Deleted.count(MBB) || !MBB->pred_empty()) 102 continue; 103 SmallVector<MachineBasicBlock *, 4> Succs(MBB->successors()); 104 WL.append(MBB->succ_begin(), MBB->succ_end()); 105 for (auto *Succ : Succs) 106 MBB->removeSuccessor(Succ); 107 // To prevent deleting the same BB multiple times, which can happen when 108 // 'MBBs' contain both a parent and a child 109 Deleted.insert(MBB); 110 MBB->eraseFromParent(); 111 } 112 } 113 114 bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) { 115 LLVM_DEBUG(dbgs() << "********** Late EH Prepare **********\n" 116 "********** Function: " 117 << MF.getName() << '\n'); 118 119 if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() != 120 ExceptionHandling::Wasm) 121 return false; 122 123 bool Changed = false; 124 if (MF.getFunction().hasPersonalityFn()) { 125 Changed |= removeUnreachableEHPads(MF); 126 recordCatchRetBBs(MF); 127 Changed |= hoistCatches(MF); 128 Changed |= addCatchAlls(MF); 129 Changed |= replaceFuncletReturns(MF); 130 } 131 Changed |= removeUnnecessaryUnreachables(MF); 132 if (MF.getFunction().hasPersonalityFn()) 133 Changed |= restoreStackPointer(MF); 134 return Changed; 135 } 136 137 // Remove unreachable EH pads and its children. If they remain, CFG 138 // stackification can be tricky. 139 bool WebAssemblyLateEHPrepare::removeUnreachableEHPads(MachineFunction &MF) { 140 SmallVector<MachineBasicBlock *, 4> ToDelete; 141 for (auto &MBB : MF) 142 if (MBB.isEHPad() && MBB.pred_empty()) 143 ToDelete.push_back(&MBB); 144 eraseDeadBBsAndChildren(ToDelete); 145 return !ToDelete.empty(); 146 } 147 148 // Record which BB ends with catchret instruction, because this will be replaced 149 // with 'br's later. This set of catchret BBs is necessary in 'getMatchingEHPad' 150 // function. 151 void WebAssemblyLateEHPrepare::recordCatchRetBBs(MachineFunction &MF) { 152 CatchRetBBs.clear(); 153 for (auto &MBB : MF) { 154 auto Pos = MBB.getFirstTerminator(); 155 if (Pos == MBB.end()) 156 continue; 157 MachineInstr *TI = &*Pos; 158 if (TI->getOpcode() == WebAssembly::CATCHRET) 159 CatchRetBBs.insert(&MBB); 160 } 161 } 162 163 // Hoist catch instructions to the beginning of their matching EH pad BBs in 164 // case, 165 // (1) catch instruction is not the first instruction in EH pad. 166 // ehpad: 167 // some_other_instruction 168 // ... 169 // %exn = catch 0 170 // (2) catch instruction is in a non-EH pad BB. For example, 171 // ehpad: 172 // br bb0 173 // bb0: 174 // %exn = catch 0 175 bool WebAssemblyLateEHPrepare::hoistCatches(MachineFunction &MF) { 176 bool Changed = false; 177 SmallVector<MachineInstr *, 16> Catches; 178 for (auto &MBB : MF) 179 for (auto &MI : MBB) 180 if (WebAssembly::isCatch(MI.getOpcode())) 181 Catches.push_back(&MI); 182 183 for (auto *Catch : Catches) { 184 MachineBasicBlock *EHPad = getMatchingEHPad(Catch); 185 assert(EHPad && "No matching EH pad for catch"); 186 auto InsertPos = EHPad->begin(); 187 // Skip EH_LABELs in the beginning of an EH pad if present. We don't use 188 // these labels at the moment, but other targets also seem to have an 189 // EH_LABEL instruction in the beginning of an EH pad. 190 while (InsertPos != EHPad->end() && InsertPos->isEHLabel()) 191 InsertPos++; 192 if (InsertPos == Catch) 193 continue; 194 Changed = true; 195 EHPad->insert(InsertPos, Catch->removeFromParent()); 196 } 197 return Changed; 198 } 199 200 // Add catch_all to beginning of cleanup pads. 201 bool WebAssemblyLateEHPrepare::addCatchAlls(MachineFunction &MF) { 202 bool Changed = false; 203 const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo(); 204 205 for (auto &MBB : MF) { 206 if (!MBB.isEHPad()) 207 continue; 208 auto InsertPos = MBB.begin(); 209 // Skip EH_LABELs in the beginning of an EH pad if present. 210 while (InsertPos != MBB.end() && InsertPos->isEHLabel()) 211 InsertPos++; 212 // This runs after hoistCatches(), so we assume that if there is a catch, 213 // that should be the first non-EH-label instruction in an EH pad. 214 if (InsertPos == MBB.end() || 215 !WebAssembly::isCatch(InsertPos->getOpcode())) { 216 Changed = true; 217 BuildMI(MBB, InsertPos, 218 InsertPos == MBB.end() ? DebugLoc() : InsertPos->getDebugLoc(), 219 TII.get(WebAssembly::CATCH_ALL)); 220 } 221 } 222 return Changed; 223 } 224 225 // Replace pseudo-instructions catchret and cleanupret with br and rethrow 226 // respectively. 227 bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) { 228 bool Changed = false; 229 const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo(); 230 231 for (auto &MBB : MF) { 232 auto Pos = MBB.getFirstTerminator(); 233 if (Pos == MBB.end()) 234 continue; 235 MachineInstr *TI = &*Pos; 236 237 switch (TI->getOpcode()) { 238 case WebAssembly::CATCHRET: { 239 // Replace a catchret with a branch 240 MachineBasicBlock *TBB = TI->getOperand(0).getMBB(); 241 if (!MBB.isLayoutSuccessor(TBB)) 242 BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::BR)) 243 .addMBB(TBB); 244 TI->eraseFromParent(); 245 Changed = true; 246 break; 247 } 248 case WebAssembly::RETHROW: 249 // These RETHROWs here were lowered from llvm.wasm.rethrow() intrinsics, 250 // generated in Clang for when an exception is not caught by the given 251 // type (e.g. catch (int)). 252 // 253 // RETHROW's BB argument is the EH pad where the exception to rethrow has 254 // been caught. (Until this point, RETHROW has just a '0' as a placeholder 255 // argument.) For these llvm.wasm.rethrow()s, we can safely assume the 256 // exception comes from the nearest dominating EH pad, because catch.start 257 // EH pad is structured like this: 258 // 259 // catch.start: 260 // catchpad ... 261 // %matches = compare ehselector with typeid 262 // br i1 %matches, label %catch, label %rethrow 263 // 264 // rethrow: 265 // ;; rethrows the exception caught in 'catch.start' 266 // call @llvm.wasm.rethrow() 267 TI->removeOperand(0); 268 TI->addOperand(MachineOperand::CreateMBB(getMatchingEHPad(TI))); 269 Changed = true; 270 break; 271 case WebAssembly::CLEANUPRET: { 272 // CLEANUPRETs have the EH pad BB the exception to rethrow has been caught 273 // as an argument. Use it and change the instruction opcode to 'RETHROW' 274 // to make rethrowing instructions consistent. 275 // 276 // This is because we cannot safely assume that it is always the nearest 277 // dominating EH pad, in case there are code transformations such as 278 // inlining. 279 BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW)) 280 .addMBB(TI->getOperand(0).getMBB()); 281 TI->eraseFromParent(); 282 Changed = true; 283 break; 284 } 285 } 286 } 287 return Changed; 288 } 289 290 // Remove unnecessary unreachables after a throw or rethrow. 291 bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables( 292 MachineFunction &MF) { 293 bool Changed = false; 294 for (auto &MBB : MF) { 295 for (auto &MI : MBB) { 296 if (MI.getOpcode() != WebAssembly::THROW && 297 MI.getOpcode() != WebAssembly::RETHROW) 298 continue; 299 Changed = true; 300 301 // The instruction after the throw should be an unreachable or a branch to 302 // another BB that should eventually lead to an unreachable. Delete it 303 // because throw itself is a terminator, and also delete successors if 304 // any. 305 MBB.erase(std::next(MI.getIterator()), MBB.end()); 306 SmallVector<MachineBasicBlock *, 8> Succs(MBB.successors()); 307 for (auto *Succ : Succs) 308 if (!Succ->isEHPad()) 309 MBB.removeSuccessor(Succ); 310 eraseDeadBBsAndChildren(Succs); 311 } 312 } 313 314 return Changed; 315 } 316 317 // After the stack is unwound due to a thrown exception, the __stack_pointer 318 // global can point to an invalid address. This inserts instructions that 319 // restore __stack_pointer global. 320 bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) { 321 const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>( 322 MF.getSubtarget().getFrameLowering()); 323 if (!FrameLowering->needsPrologForEH(MF)) 324 return false; 325 bool Changed = false; 326 327 for (auto &MBB : MF) { 328 if (!MBB.isEHPad()) 329 continue; 330 Changed = true; 331 332 // Insert __stack_pointer restoring instructions at the beginning of each EH 333 // pad, after the catch instruction. Here it is safe to assume that SP32 334 // holds the latest value of __stack_pointer, because the only exception for 335 // this case is when a function uses the red zone, but that only happens 336 // with leaf functions, and we don't restore __stack_pointer in leaf 337 // functions anyway. 338 auto InsertPos = MBB.begin(); 339 // Skip EH_LABELs in the beginning of an EH pad if present. 340 while (InsertPos != MBB.end() && InsertPos->isEHLabel()) 341 InsertPos++; 342 assert(InsertPos != MBB.end() && 343 WebAssembly::isCatch(InsertPos->getOpcode()) && 344 "catch/catch_all should be present in every EH pad at this point"); 345 ++InsertPos; // Skip the catch instruction 346 FrameLowering->writeSPToGlobal(FrameLowering->getSPReg(MF), MF, MBB, 347 InsertPos, MBB.begin()->getDebugLoc()); 348 } 349 return Changed; 350 } 351