| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,331 @@ | ||
| //===-- WasmEHPrepare - Prepare excepton handling for WebAssembly --------===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This transformation is designed for use by code generators which use | ||
| // WebAssembly exception handling scheme. | ||
| // | ||
| // WebAssembly exception handling uses Windows exception IR for the middle level | ||
| // representation. This pass does the following transformation for every | ||
| // catchpad block: | ||
| // (In C-style pseudocode) | ||
| // | ||
| // - Before: | ||
| // catchpad ... | ||
| // exn = wasm.get.exception(); | ||
| // selector = wasm.get.selector(); | ||
| // ... | ||
| // | ||
| // - After: | ||
| // catchpad ... | ||
| // exn = wasm.catch(0); // 0 is a tag for C++ | ||
| // wasm.landingpad.index(index); | ||
| // // Only add below in case it's not a single catch (...) | ||
| // __wasm_lpad_context.lpad_index = index; | ||
| // __wasm_lpad_context.lsda = wasm.lsda(); | ||
| // _Unwind_CallPersonality(exn); | ||
| // int selector = __wasm.landingpad_context.selector; | ||
| // ... | ||
| // | ||
| // Also, does the following for a cleanuppad block with a call to | ||
| // __clang_call_terminate(): | ||
| // - Before: | ||
| // cleanuppad ... | ||
| // exn = wasm.get.exception(); | ||
| // __clang_call_terminate(exn); | ||
| // | ||
| // - After: | ||
| // cleanuppad ... | ||
| // exn = wasm.catch(0); // 0 is a tag for C++ | ||
| // __clang_call_terminate(exn); | ||
| // | ||
| // | ||
| // * Background: WebAssembly EH instructions | ||
| // WebAssembly's try and catch instructions are structured as follows: | ||
| // try | ||
| // instruction* | ||
| // catch (C++ tag) | ||
| // instruction* | ||
| // ... | ||
| // catch_all | ||
| // instruction* | ||
| // try_end | ||
| // | ||
| // A catch instruction in WebAssembly does not correspond to a C++ catch clause. | ||
| // In WebAssembly, there is a single catch instruction for all C++ exceptions. | ||
| // There can be more catch instructions for exceptions in other languages, but | ||
| // they are not generated for now. catch_all catches all exceptions including | ||
| // foreign exceptions (e.g. JavaScript). We turn catchpads into catch (C++ tag) | ||
| // and cleanuppads into catch_all, with one exception: cleanuppad with a call to | ||
| // __clang_call_terminate should be both in catch (C++ tag) and catch_all. | ||
| // | ||
| // | ||
| // * Background: Direct personality function call | ||
| // In WebAssembly EH, the VM is responsible for unwinding the stack once an | ||
| // exception is thrown. After the stack is unwound, the control flow is | ||
| // transfered to WebAssembly 'catch' instruction, which returns a caught | ||
| // exception object. | ||
| // | ||
| // Unwinding the stack is not done by libunwind but the VM, so the personality | ||
| // function in libcxxabi cannot be called from libunwind during the unwinding | ||
| // process. So after a catch instruction, we insert a call to a wrapper function | ||
| // in libunwind that in turn calls the real personality function. | ||
| // | ||
| // In Itanium EH, if the personality function decides there is no matching catch | ||
| // clause in a call frame and no cleanup action to perform, the unwinder doesn't | ||
| // stop there and continues unwinding. But in Wasm EH, the unwinder stops at | ||
| // every call frame with a catch intruction, after which the personality | ||
| // function is called from the compiler-generated user code here. | ||
| // | ||
| // In libunwind, we have this struct that serves as a communincation channel | ||
| // between the compiler-generated user code and the personality function in | ||
| // libcxxabi. | ||
| // | ||
| // struct _Unwind_LandingPadContext { | ||
| // uintptr_t lpad_index; | ||
| // uintptr_t lsda; | ||
| // uintptr_t selector; | ||
| // }; | ||
| // struct _Unwind_LandingPadContext __wasm_lpad_context = ...; | ||
| // | ||
| // And this wrapper in libunwind calls the personality function. | ||
| // | ||
| // _Unwind_Reason_Code _Unwind_CallPersonality(void *exception_ptr) { | ||
| // struct _Unwind_Exception *exception_obj = | ||
| // (struct _Unwind_Exception *)exception_ptr; | ||
| // _Unwind_Reason_Code ret = __gxx_personality_v0( | ||
| // 1, _UA_CLEANUP_PHASE, exception_obj->exception_class, exception_obj, | ||
| // (struct _Unwind_Context *)__wasm_lpad_context); | ||
| // return ret; | ||
| // } | ||
| // | ||
| // We pass a landing pad index, and the address of LSDA for the current function | ||
| // to the wrapper function _Unwind_CallPersonality in libunwind, and we retrieve | ||
| // the selector after it returns. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "llvm/ADT/SetVector.h" | ||
| #include "llvm/ADT/Statistic.h" | ||
| #include "llvm/ADT/Triple.h" | ||
| #include "llvm/CodeGen/Passes.h" | ||
| #include "llvm/CodeGen/TargetLowering.h" | ||
| #include "llvm/CodeGen/TargetSubtargetInfo.h" | ||
| #include "llvm/IR/Dominators.h" | ||
| #include "llvm/IR/IRBuilder.h" | ||
| #include "llvm/IR/Intrinsics.h" | ||
| #include "llvm/Pass.h" | ||
| #include "llvm/Transforms/Utils/BasicBlockUtils.h" | ||
|
|
||
| using namespace llvm; | ||
|
|
||
| #define DEBUG_TYPE "wasmehprepare" | ||
|
|
||
| namespace { | ||
| class WasmEHPrepare : public FunctionPass { | ||
| Type *LPadContextTy = nullptr; // type of 'struct _Unwind_LandingPadContext' | ||
| GlobalVariable *LPadContextGV = nullptr; // __wasm_lpad_context | ||
|
|
||
| // Field addresses of struct _Unwind_LandingPadContext | ||
| Value *LPadIndexField = nullptr; // lpad_index field | ||
| Value *LSDAField = nullptr; // lsda field | ||
| Value *SelectorField = nullptr; // selector | ||
|
|
||
| Function *CatchF = nullptr; // wasm.catch.extract() intrinsic | ||
| Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic | ||
| Function *LSDAF = nullptr; // wasm.lsda() intrinsic | ||
| Function *GetExnF = nullptr; // wasm.get.exception() intrinsic | ||
| Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic | ||
| Function *CallPersonalityF = nullptr; // _Unwind_CallPersonality() wrapper | ||
| Function *ClangCallTermF = nullptr; // __clang_call_terminate() function | ||
|
|
||
| void prepareEHPad(BasicBlock *BB, unsigned Index); | ||
| void prepareTerminateCleanupPad(BasicBlock *BB); | ||
|
|
||
| public: | ||
| static char ID; // Pass identification, replacement for typeid | ||
|
|
||
| WasmEHPrepare() : FunctionPass(ID) {} | ||
|
|
||
| bool doInitialization(Module &M) override; | ||
| bool runOnFunction(Function &F) override; | ||
|
|
||
| StringRef getPassName() const override { | ||
| return "WebAssembly Exception handling preparation"; | ||
| } | ||
| }; | ||
| } // end anonymous namespace | ||
|
|
||
| char WasmEHPrepare::ID = 0; | ||
| INITIALIZE_PASS(WasmEHPrepare, DEBUG_TYPE, "Prepare WebAssembly exceptions", | ||
| false, false); | ||
|
|
||
| FunctionPass *llvm::createWasmEHPass() { return new WasmEHPrepare(); } | ||
|
|
||
| bool WasmEHPrepare::doInitialization(Module &M) { | ||
| IRBuilder<> IRB(M.getContext()); | ||
| LPadContextTy = StructType::get(IRB.getInt32Ty(), // lpad_index | ||
| IRB.getInt8PtrTy(), // lsda | ||
| IRB.getInt32Ty() // selector | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| bool WasmEHPrepare::runOnFunction(Function &F) { | ||
| SmallVector<BasicBlock *, 16> CatchPads; | ||
| SmallVector<BasicBlock *, 16> CleanupPads; | ||
| for (BasicBlock &BB : F) { | ||
| if (!BB.isEHPad()) | ||
| continue; | ||
| auto *Pad = BB.getFirstNonPHI(); | ||
| if (isa<CatchPadInst>(Pad)) | ||
| CatchPads.push_back(&BB); | ||
| else if (isa<CleanupPadInst>(Pad)) | ||
| CleanupPads.push_back(&BB); | ||
| } | ||
|
|
||
| if (CatchPads.empty() && CleanupPads.empty()) | ||
| return false; | ||
| assert(F.hasPersonalityFn() && "Personality function not found"); | ||
|
|
||
| Module &M = *F.getParent(); | ||
| IRBuilder<> IRB(F.getContext()); | ||
|
|
||
| // __wasm_lpad_context global variable | ||
| LPadContextGV = cast<GlobalVariable>( | ||
| M.getOrInsertGlobal("__wasm_lpad_context", LPadContextTy)); | ||
| LPadIndexField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 0, | ||
| "lpad_index_gep"); | ||
| LSDAField = | ||
| IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 1, "lsda_gep"); | ||
| SelectorField = IRB.CreateConstGEP2_32(LPadContextTy, LPadContextGV, 0, 2, | ||
| "selector_gep"); | ||
|
|
||
| // wasm.catch() intinsic, which will be lowered to wasm 'catch' instruction. | ||
| CatchF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_catch); | ||
| // wasm.landingpad.index() intrinsic, which is to specify landingpad index | ||
| LPadIndexF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_landingpad_index); | ||
| // wasm.lsda() intrinsic. Returns the address of LSDA table for the current | ||
| // function. | ||
| LSDAF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_lsda); | ||
| // wasm.get.exception() and wasm.get.ehselector() intrinsics. Calls to these | ||
| // are generated in clang. | ||
| GetExnF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_exception); | ||
| GetSelectorF = Intrinsic::getDeclaration(&M, Intrinsic::wasm_get_ehselector); | ||
|
|
||
| // _Unwind_CallPersonality() wrapper function, which calls the personality | ||
| CallPersonalityF = cast<Function>(M.getOrInsertFunction( | ||
| "_Unwind_CallPersonality", IRB.getInt32Ty(), IRB.getInt8PtrTy())); | ||
| CallPersonalityF->setDoesNotThrow(); | ||
|
|
||
| // __clang_call_terminate() function, which is inserted by clang in case a | ||
| // cleanup throws | ||
| ClangCallTermF = M.getFunction("__clang_call_terminate"); | ||
|
|
||
| unsigned Index = 0; | ||
| for (auto *BB : CatchPads) { | ||
| auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHI()); | ||
| // In case of a single catch (...), we don't need to emit LSDA | ||
| if (CPI->getNumArgOperands() == 1 && | ||
| cast<Constant>(CPI->getArgOperand(0))->isNullValue()) | ||
| prepareEHPad(BB, -1); | ||
| else | ||
| prepareEHPad(BB, Index++); | ||
| } | ||
|
|
||
| if (!ClangCallTermF) | ||
| return !CatchPads.empty(); | ||
|
|
||
| // Cleanuppads will turn into catch_all later, but cleanuppads with a call to | ||
| // __clang_call_terminate() is a special case. __clang_call_terminate() takes | ||
| // an exception object, so we have to duplicate call in both 'catch <C++ tag>' | ||
| // and 'catch_all' clauses. Here we only insert a call to catch; the | ||
| // duplication will be done later. In catch_all, the exception object will be | ||
| // set to null. | ||
| for (auto *BB : CleanupPads) | ||
| for (auto &I : *BB) | ||
| if (auto *CI = dyn_cast<CallInst>(&I)) | ||
| if (CI->getCalledValue() == ClangCallTermF) | ||
| prepareEHPad(BB, -1); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| void WasmEHPrepare::prepareEHPad(BasicBlock *BB, unsigned Index) { | ||
| assert(BB->isEHPad() && "BB is not an EHPad!"); | ||
| IRBuilder<> IRB(BB->getContext()); | ||
|
|
||
| IRB.SetInsertPoint(&*BB->getFirstInsertionPt()); | ||
| // The argument to wasm.catch() is the tag for C++ exceptions, which we set to | ||
| // 0 for this module. | ||
| // Pseudocode: void *exn = wasm.catch(0); | ||
| Instruction *Exn = IRB.CreateCall(CatchF, IRB.getInt32(0), "exn"); | ||
| // Replace the return value of wasm.get.exception() with the return value from | ||
| // wasm.catch(). | ||
| auto *FPI = cast<FuncletPadInst>(BB->getFirstNonPHI()); | ||
| Instruction *GetExnCI = nullptr, *GetSelectorCI = nullptr; | ||
| for (auto &U : FPI->uses()) { | ||
| if (auto *CI = dyn_cast<CallInst>(U.getUser())) { | ||
| if (CI->getCalledValue() == GetExnF) | ||
| GetExnCI = CI; | ||
| else if (CI->getCalledValue() == GetSelectorF) | ||
| GetSelectorCI = CI; | ||
| } | ||
| } | ||
|
|
||
| assert(GetExnCI && "wasm.get.exception() call does not exist"); | ||
| GetExnCI->replaceAllUsesWith(Exn); | ||
| GetExnCI->eraseFromParent(); | ||
|
|
||
| // In case it is a catchpad with single catch (...) or a cleanuppad, we don't | ||
| // need to call personality function because we don't need a selector. | ||
| if (FPI->getNumArgOperands() == 0 || | ||
| (FPI->getNumArgOperands() == 1 && | ||
| cast<Constant>(FPI->getArgOperand(0))->isNullValue())) { | ||
| if (GetSelectorCI) { | ||
| assert(GetSelectorCI->use_empty() && | ||
| "wasm.get.ehselector() still has uses!"); | ||
| GetSelectorCI->eraseFromParent(); | ||
| } | ||
| return; | ||
| } | ||
| IRB.SetInsertPoint(Exn->getNextNode()); | ||
|
|
||
| // This is to create a map of <landingpad EH label, landingpad index> in | ||
| // SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables. | ||
| // Pseudocode: wasm.landingpad.index(Index); | ||
| IRB.CreateCall(LPadIndexF, IRB.getInt32(Index)); | ||
|
|
||
| // Pseudocode: __wasm_lpad_context.lpad_index = index; | ||
| IRB.CreateStore(IRB.getInt32(Index), LPadIndexField); | ||
|
|
||
| // Store LSDA address only if this catchpad belongs to a top-level | ||
| // catchswitch. If there is another catchpad that dominates this pad, we don't | ||
| // need to store LSDA address again, because they are the same throughout the | ||
| // function and have been already stored before. | ||
| // TODO Can we not store LSDA address in user function but make libcxxabi | ||
| // compute it? | ||
| auto *CPI = cast<CatchPadInst>(FPI); | ||
| if (isa<ConstantTokenNone>(CPI->getCatchSwitch()->getParentPad())) | ||
| // Pseudocode: __wasm_lpad_context.lsda = wasm.lsda(); | ||
| IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField); | ||
|
|
||
| // Pseudocode: _Unwind_CallPersonality(exn); | ||
| CallInst *PersCI = | ||
| IRB.CreateCall(CallPersonalityF, Exn, OperandBundleDef("funclet", CPI)); | ||
| PersCI->setDoesNotThrow(); | ||
|
|
||
| // Pseudocode: int selector = __wasm.landingpad_context.selector; | ||
| Instruction *Selector = IRB.CreateLoad(SelectorField, "selector"); | ||
|
|
||
| // Replace the return value from wasm.get.ehselector() with the selector value | ||
| // loaded from __wasm_lpad_context.selector. | ||
| assert(GetSelectorCI && "wasm.get.ehselector() call does not exist"); | ||
| GetSelectorCI->replaceAllUsesWith(Selector); | ||
| GetSelectorCI->eraseFromParent(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,317 @@ | ||
| ; RUN: opt < %s -winehprepare -demote-catchswitch-only -wasmehprepare -S | FileCheck %s | ||
|
|
||
| target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" | ||
| target triple = "wasm32-unknown-unknown" | ||
|
|
||
| ; CHECK: @__wasm_lpad_context = external global { i32, i8*, i32 } | ||
|
|
||
| @_ZTIi = external constant i8* | ||
| %struct.Cleanup = type { i8 } | ||
|
|
||
| ; A single 'catch (int)' clause. | ||
| ; A wasm.catch() call, wasm.lsda() call, and personality call to generate a | ||
| ; selector should all be genereated after the catchpad. | ||
| define void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { | ||
| ; CHECK-LABEL: @test0() | ||
| entry: | ||
| invoke void @foo() | ||
| to label %try.cont unwind label %catch.dispatch | ||
|
|
||
| catch.dispatch: ; preds = %entry | ||
| %0 = catchswitch within none [label %catch.start] unwind to caller | ||
|
|
||
| catch.start: ; preds = %catch.dispatch | ||
| %1 = catchpad within %0 [i8* bitcast (i8** @_ZTIi to i8*)] | ||
| %2 = call i8* @llvm.wasm.get.exception(token %1) | ||
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | ||
| %4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) | ||
| %matches = icmp eq i32 %3, %4 | ||
| br i1 %matches, label %catch, label %rethrow | ||
| ; CHECK: catch.start: | ||
| ; CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad | ||
| ; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.catch(i32 0) | ||
| ; CHECK-NEXT: call void @llvm.wasm.landingpad.index(i32 0) | ||
| ; CHECK-NEXT: store i32 0, i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 0) | ||
| ; CHECK-NEXT: %[[LSDA:.*]] = call i8* @llvm.wasm.lsda() | ||
| ; CHECK-NEXT: store i8* %[[LSDA]], i8** getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 1) | ||
| ; CHECK-NEXT: call i32 @_Unwind_CallPersonality(i8* %[[EXN]]) {{.*}} [ "funclet"(token %[[CATCHPAD]]) ] | ||
| ; CHECK-NEXT: %[[SELECTOR:.*]] = load i32, i32* getelementptr inbounds ({ i32, i8*, i32 }, { i32, i8*, i32 }* @__wasm_lpad_context, i32 0, i32 2) | ||
| ; CHECK: icmp eq i32 %[[SELECTOR]] | ||
|
|
||
| catch: ; preds = %catch.start | ||
| %5 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | ||
| catchret from %1 to label %try.cont | ||
| ; CHECK: catch: | ||
| ; CHECK-NEXT: call i8* @__cxa_begin_catch(i8* %[[EXN]]) | ||
|
|
||
| rethrow: ; preds = %catch.start | ||
| call void @__cxa_rethrow() [ "funclet"(token %1) ] | ||
| unreachable | ||
|
|
||
| try.cont: ; preds = %entry, %catch | ||
| ret void | ||
| } | ||
|
|
||
| ; Two try-catches, one of them is with a single 'catch (...)' clause. | ||
| ; For the catchpad with a single 'catch (...)', only a wasm.catch() call should | ||
| ; be generated after the catchpad; wasm.landingpad.index() and personality call | ||
| ; should NOT be generated. For the other catchpad, the argument of | ||
| ; wasm.landingpad.index() should be not 1 but 0. | ||
| define void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { | ||
| ; CHECK-LABEL: @test1() | ||
| entry: | ||
| invoke void @foo() | ||
| to label %try.cont unwind label %catch.dispatch | ||
|
|
||
| catch.dispatch: ; preds = %entry | ||
| %0 = catchswitch within none [label %catch.start] unwind to caller | ||
|
|
||
| catch.start: ; preds = %catch.dispatch | ||
| %1 = catchpad within %0 [i8* null] | ||
| %2 = call i8* @llvm.wasm.get.exception(token %1) | ||
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | ||
| %4 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | ||
| catchret from %1 to label %try.cont | ||
| ; CHECK: catch.start: | ||
| ; CHECK-NEXT: catchpad within %0 [i8* null] | ||
| ; CHECK-NEXT: call i8* @llvm.wasm.catch(i32 0) | ||
| ; CHECK-NOT: call void @llvm.wasm.landingpad.index | ||
| ; CHECK-NOT: store {{.*}} @__wasm_lpad_context | ||
| ; CHECK-NOT: call i8* @llvm.wasm.lsda() | ||
| ; CHECK-NOT: call i32 @_Unwind_CallPersonality | ||
| ; CHECK-NOT: load {{.*}} @__wasm_lpad_context | ||
|
|
||
| try.cont: ; preds = %entry, %catch.start | ||
| invoke void @foo() | ||
| to label %try.cont7 unwind label %catch.dispatch2 | ||
|
|
||
| catch.dispatch2: ; preds = %try.cont | ||
| %5 = catchswitch within none [label %catch.start3] unwind to caller | ||
|
|
||
| catch.start3: ; preds = %catch.dispatch2 | ||
| %6 = catchpad within %5 [i8* bitcast (i8** @_ZTIi to i8*)] | ||
| %7 = call i8* @llvm.wasm.get.exception(token %6) | ||
| %8 = call i32 @llvm.wasm.get.ehselector(token %6) | ||
| %9 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) | ||
| %matches = icmp eq i32 %8, %9 | ||
| br i1 %matches, label %catch4, label %rethrow | ||
| ; CHECK: catch.start3: | ||
| ; CHECK: call void @llvm.wasm.landingpad.index(i32 0) | ||
|
|
||
| catch4: ; preds = %catch.start3 | ||
| %10 = call i8* @__cxa_begin_catch(i8* %7) [ "funclet"(token %6) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %6) ] | ||
| catchret from %6 to label %try.cont7 | ||
|
|
||
| rethrow: ; preds = %catch.start3 | ||
| call void @__cxa_rethrow() [ "funclet"(token %6) ] | ||
| unreachable | ||
|
|
||
| try.cont7: ; preds = %try.cont, %catch4 | ||
| ret void | ||
| } | ||
|
|
||
| ; A nested try-catch within a catch. Within the nested catchpad, wasm.lsda() | ||
| ; call should NOT be generated. | ||
| define void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { | ||
| ; CHECK-LABEL: @test2() | ||
| entry: | ||
| invoke void @foo() | ||
| to label %try.cont9 unwind label %catch.dispatch | ||
|
|
||
| catch.dispatch: ; preds = %entry | ||
| %0 = catchswitch within none [label %catch.start] unwind to caller | ||
|
|
||
| catch.start: ; preds = %catch.dispatch | ||
| %1 = catchpad within %0 [i8* bitcast (i8** @_ZTIi to i8*)] | ||
| %2 = call i8* @llvm.wasm.get.exception(token %1) | ||
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | ||
| %4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) | ||
| %matches = icmp eq i32 %3, %4 | ||
| br i1 %matches, label %catch, label %rethrow | ||
| ; CHECK: catch.start: | ||
| ; CHECK: call i8* @llvm.wasm.lsda() | ||
|
|
||
| catch: ; preds = %catch.start | ||
| %5 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ] | ||
| invoke void @foo() [ "funclet"(token %1) ] | ||
| to label %try.cont unwind label %catch.dispatch2 | ||
|
|
||
| catch.dispatch2: ; preds = %catch | ||
| %6 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup | ||
|
|
||
| catch.start3: ; preds = %catch.dispatch2 | ||
| %7 = catchpad within %6 [i8* bitcast (i8** @_ZTIi to i8*)] | ||
| %8 = call i8* @llvm.wasm.get.exception(token %7) | ||
| %9 = call i32 @llvm.wasm.get.ehselector(token %7) | ||
| %10 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) | ||
| %matches4 = icmp eq i32 %9, %10 | ||
| br i1 %matches4, label %catch6, label %rethrow5 | ||
| ; CHECK: catch.start3: | ||
| ; CHECK-NOT: call i8* @llvm.wasm.lsda() | ||
|
|
||
| catch6: ; preds = %catch.start3 | ||
| %11 = call i8* @__cxa_begin_catch(i8* %8) [ "funclet"(token %7) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %7) ] | ||
| catchret from %7 to label %try.cont | ||
|
|
||
| rethrow5: ; preds = %catch.start3 | ||
| invoke void @__cxa_rethrow() [ "funclet"(token %7) ] | ||
| to label %unreachable unwind label %ehcleanup | ||
|
|
||
| try.cont: ; preds = %catch, %catch6 | ||
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | ||
| catchret from %1 to label %try.cont9 | ||
|
|
||
| rethrow: ; preds = %catch.start | ||
| call void @__cxa_rethrow() [ "funclet"(token %1) ] | ||
| unreachable | ||
|
|
||
| try.cont9: ; preds = %entry, %try.cont | ||
| ret void | ||
|
|
||
| ehcleanup: ; preds = %rethrow5, %catch.dispatch2 | ||
| %12 = cleanuppad within %1 [] | ||
| call void @__cxa_end_catch() [ "funclet"(token %12) ] | ||
| cleanupret from %12 unwind to caller | ||
| ; CHECK: ehcleanup: | ||
| ; CHECK-NEXT: cleanuppad | ||
| ; CHECK-NOT: call i8* @llvm.wasm.catch(i32 0) | ||
| ; CHECK-NOT: call void @llvm.wasm.landingpad.index | ||
| ; CHECK-NOT: store {{.*}} @__wasm_lpad_context | ||
| ; CHECK-NOT: call i8* @llvm.wasm.lsda() | ||
| ; CHECK-NOT: call i32 @_Unwind_CallPersonality | ||
| ; CHECK-NOT: load {{.*}} @__wasm_lpad_context | ||
|
|
||
| unreachable: ; preds = %rethrow5 | ||
| unreachable | ||
| } | ||
|
|
||
| ; A cleanuppad with a call to __clang_call_terminate(). | ||
| ; A call to wasm.catch() should be generated after the cleanuppad. | ||
| define hidden void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { | ||
| ; CHECK-LABEL: @test3 | ||
| entry: | ||
| invoke void @foo() | ||
| to label %try.cont unwind label %catch.dispatch | ||
|
|
||
| catch.dispatch: ; preds = %entry | ||
| %0 = catchswitch within none [label %catch.start] unwind to caller | ||
|
|
||
| catch.start: ; preds = %catch.dispatch | ||
| %1 = catchpad within %0 [i8* null] | ||
| %2 = call i8* @llvm.wasm.get.exception(token %1) | ||
| %3 = call i32 @llvm.wasm.get.ehselector(token %1) | ||
| %4 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ] | ||
| invoke void @foo() [ "funclet"(token %1) ] | ||
| to label %invoke.cont1 unwind label %ehcleanup | ||
|
|
||
| invoke.cont1: ; preds = %catch.start | ||
| call void @__cxa_end_catch() [ "funclet"(token %1) ] | ||
| catchret from %1 to label %try.cont | ||
|
|
||
| try.cont: ; preds = %entry, %invoke.cont1 | ||
| ret void | ||
|
|
||
| ehcleanup: ; preds = %catch.start | ||
| %5 = cleanuppad within %1 [] | ||
| invoke void @__cxa_end_catch() [ "funclet"(token %5) ] | ||
| to label %invoke.cont2 unwind label %terminate | ||
|
|
||
| invoke.cont2: ; preds = %ehcleanup | ||
| cleanupret from %5 unwind to caller | ||
|
|
||
| terminate: ; preds = %ehcleanup | ||
| %6 = cleanuppad within %5 [] | ||
| %7 = call i8* @llvm.wasm.get.exception(token %6) | ||
| call void @__clang_call_terminate(i8* %7) [ "funclet"(token %6) ] | ||
| unreachable | ||
| ; CHECK: terminate: | ||
| ; CHECK-NEXT: cleanuppad | ||
| ; CHECK-NEXT: %[[EXN:.*]] = call i8* @llvm.wasm.catch(i32 0) | ||
| ; CHECK-NEXT: call void @__clang_call_terminate(i8* %[[EXN]]) | ||
| } | ||
|
|
||
| ; PHI demotion test. Only the phi before catchswitch should be demoted; the phi | ||
| ; before cleanuppad should NOT. | ||
| define void @test5() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { | ||
| ; CHECK-LABEL: @test5 | ||
| entry: | ||
| %c = alloca %struct.Cleanup, align 1 | ||
| invoke void @foo() | ||
| to label %invoke.cont unwind label %ehcleanup | ||
|
|
||
| invoke.cont: ; preds = %entry | ||
| invoke void @foo() | ||
| to label %invoke.cont1 unwind label %ehcleanup | ||
|
|
||
| invoke.cont1: ; preds = %invoke.cont | ||
| %call = call %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c) | ||
| br label %try.cont | ||
|
|
||
| ehcleanup: ; preds = %invoke.cont, %entry | ||
| %num.0 = phi i32 [ 2, %invoke.cont ], [ 1, %entry ] | ||
| %0 = cleanuppad within none [] | ||
| %call2 = call %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* %c) [ "funclet"(token %0) ] | ||
| cleanupret from %0 unwind label %catch.dispatch | ||
| ; CHECK: ehcleanup: | ||
| ; CHECK-NEXT: = phi | ||
|
|
||
| catch.dispatch: ; preds = %ehcleanup | ||
| %1 = catchswitch within none [label %catch.start] unwind to caller | ||
|
|
||
| catch.start: ; preds = %catch.dispatch | ||
| %2 = catchpad within %1 [i8* null] | ||
| %3 = call i8* @llvm.wasm.get.exception(token %2) | ||
| %4 = call i32 @llvm.wasm.get.ehselector(token %2) | ||
| %5 = call i8* @__cxa_begin_catch(i8* %3) [ "funclet"(token %2) ] | ||
| call void @func(i32 %num.0) [ "funclet"(token %2) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %2) ] | ||
| catchret from %2 to label %try.cont | ||
|
|
||
| try.cont: ; preds = %catch.start, %invoke.cont1 | ||
| invoke void @foo() | ||
| to label %invoke.cont3 unwind label %catch.dispatch5 | ||
|
|
||
| invoke.cont3: ; preds = %try.cont | ||
| invoke void @foo() | ||
| to label %try.cont10 unwind label %catch.dispatch5 | ||
|
|
||
| catch.dispatch5: ; preds = %invoke.cont3, %try.cont | ||
| %num.1 = phi i32 [ 2, %invoke.cont3 ], [ 1, %try.cont ] | ||
| %6 = catchswitch within none [label %catch.start6] unwind to caller | ||
| ; CHECK: catch.dispatch5: | ||
| ; CHECK-NOT: = phi | ||
|
|
||
| catch.start6: ; preds = %catch.dispatch5 | ||
| %7 = catchpad within %6 [i8* null] | ||
| %8 = call i8* @llvm.wasm.get.exception(token %7) | ||
| %9 = call i32 @llvm.wasm.get.ehselector(token %7) | ||
| %10 = call i8* @__cxa_begin_catch(i8* %8) [ "funclet"(token %7) ] | ||
| call void @func(i32 %num.1) [ "funclet"(token %7) ] | ||
| call void @__cxa_end_catch() [ "funclet"(token %7) ] | ||
| catchret from %7 to label %try.cont10 | ||
|
|
||
| try.cont10: ; preds = %invoke.cont3, %catch.start6 | ||
| ret void | ||
| } | ||
|
|
||
| declare void @foo() | ||
| declare void @func(i32) | ||
| declare %struct.Cleanup* @_ZN7CleanupD1Ev(%struct.Cleanup* returned) | ||
| declare i32 @__gxx_wasm_personality_v0(...) | ||
| declare i8* @llvm.wasm.get.exception(token) | ||
| declare i32 @llvm.wasm.get.ehselector(token) | ||
| declare i32 @llvm.eh.typeid.for(i8*) | ||
| declare i8* @__cxa_begin_catch(i8*) | ||
| declare void @__cxa_end_catch() | ||
| declare void @__cxa_rethrow() | ||
| declare void @__clang_call_terminate(i8*) | ||
|
|
||
| ; CHECK-DAG: declare i8* @llvm.wasm.catch(i32) | ||
| ; CHECK-DAG: declare void @llvm.wasm.landingpad.index(i32) | ||
| ; CHECK-DAG: declare i8* @llvm.wasm.lsda() | ||
| ; CHECK-DAG: declare i32 @_Unwind_CallPersonality(i8*) | ||
|
|