[llvm] eb675e9 - [WebAssembly] Support Wasm EH + Wasm SjLj

Heejin Ahn via llvm-commits llvm-commits at lists.llvm.org
Wed Jan 19 20:14:38 PST 2022


Author: Heejin Ahn
Date: 2022-01-19T20:13:54-08:00
New Revision: eb675e972d742476496fc5b3ee27e31e7516224d

URL: https://github.com/llvm/llvm-project/commit/eb675e972d742476496fc5b3ee27e31e7516224d
DIFF: https://github.com/llvm/llvm-project/commit/eb675e972d742476496fc5b3ee27e31e7516224d.diff

LOG: [WebAssembly] Support Wasm EH + Wasm SjLj

D108960 added support for SjLj using Wasm EH instructions, which we call
Wasm SjLj going forward. (We call the old SjLj Emscripten SjLj) But it
did not support using Wasm EH and Wasm SjLj together. So far users of
Wasm EH had to use Wasm EH with Emscripten SjLj, which had a certain
limitation and it suffered from bigger code size increases as well.

This enables using Wasm EH and Wasm SjLj together.
1. This redirects `catchswitch` and `cleanupret` that unwind to caller
   to `catch.dispatch.longjmp` BB, which is a `catchswitch` BB that
   handles longjmps.
2. D108960 converted all longjmpable `call`s to `invokes` that unwind to
   `catch.dispatch.longjmp`. This CL checks if the `call` is embedded
   within another `catchpad`, and if so, makes it unwind to its nearest
   parent's unwind destination, rather than `catch.dispatch.longjmp`.
   This is necessary to preserve the scoping structure.

Reviewed By: dschuff

Differential Revision: https://reviews.llvm.org/D117610

Added: 
    llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll

Modified: 
    llvm/include/llvm/Transforms/Utils/Local.h
    llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
    llvm/lib/Transforms/Utils/Local.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Transforms/Utils/Local.h b/llvm/include/llvm/Transforms/Utils/Local.h
index 8a6f172b78d49..873127554b47c 100644
--- a/llvm/include/llvm/Transforms/Utils/Local.h
+++ b/llvm/include/llvm/Transforms/Utils/Local.h
@@ -241,7 +241,7 @@ inline Align getKnownAlignment(Value *V, const DataLayout &DL,
 CallInst *createCallMatchingInvoke(InvokeInst *II);
 
 /// This function converts the specified invoek into a normall call.
-void changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr);
+CallInst *changeToCall(InvokeInst *II, DomTreeUpdater *DTU = nullptr);
 
 ///===---------------------------------------------------------------------===//
 ///  Dbg Intrinsic utilities

diff  --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index 33cda4d000922..fe656753889f9 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -611,6 +611,8 @@ static bool canLongjmp(const Value *Callee) {
     return false;
   StringRef CalleeName = Callee->getName();
 
+  // TODO Include more functions or consider checking with mangled prefixes
+
   // The reason we include malloc/free here is to exclude the malloc/free
   // calls generated in setjmp prep / cleanup routines.
   if (CalleeName == "setjmp" || CalleeName == "malloc" || CalleeName == "free")
@@ -627,11 +629,50 @@ static bool canLongjmp(const Value *Callee) {
     return false;
 
   // Exception-catching related functions
-  if (CalleeName == "__cxa_begin_catch" || CalleeName == "__cxa_end_catch" ||
+  //
+  // We intentionally excluded __cxa_end_catch here even though it surely cannot
+  // longjmp, in order to maintain the unwind relationship from all existing
+  // catchpads (and calls within them) to catch.dispatch.longjmp.
+  //
+  // In Wasm EH + Wasm SjLj, we
+  // 1. Make all catchswitch and cleanuppad that unwind to caller unwind to
+  //    catch.dispatch.longjmp instead
+  // 2. Convert all longjmpable calls to invokes that unwind to
+  //    catch.dispatch.longjmp
+  // But catchswitch BBs are removed in isel, so if an EH catchswitch (generated
+  // from an exception)'s catchpad does not contain any calls that are converted
+  // into invokes unwinding to catch.dispatch.longjmp, this unwind relationship
+  // (EH catchswitch BB -> catch.dispatch.longjmp BB) is lost and
+  // catch.dispatch.longjmp BB can be placed before the EH catchswitch BB in
+  // CFGSort.
+  // int ret = setjmp(buf);
+  // try {
+  //   foo(); // longjmps
+  // } catch (...) {
+  // }
+  // Then in this code, if 'foo' longjmps, it first unwinds to 'catch (...)'
+  // catchswitch, and is not caught by that catchswitch because it is a longjmp,
+  // then it should next unwind to catch.dispatch.longjmp BB. But if this 'catch
+  // (...)' catchswitch -> catch.dispatch.longjmp unwind relationship is lost,
+  // it will not unwind to catch.dispatch.longjmp, producing an incorrect
+  // result.
+  //
+  // Every catchpad generated by Wasm C++ contains __cxa_end_catch, so we
+  // intentionally treat it as longjmpable to work around this problem. This is
+  // a hacky fix but an easy one.
+  //
+  // The comment block in findWasmUnwindDestinations() in
+  // SelectionDAGBuilder.cpp is addressing a similar problem.
+  if (CalleeName == "__cxa_begin_catch" ||
       CalleeName == "__cxa_allocate_exception" || CalleeName == "__cxa_throw" ||
       CalleeName == "__clang_call_terminate")
     return false;
 
+  // std::terminate, which is generated when another exception occurs while
+  // handling an exception, cannot longjmp.
+  if (CalleeName == "_ZSt9terminatev")
+    return false;
+
   // Otherwise we don't know
   return true;
 }
@@ -1271,16 +1312,19 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
   // Setjmp transformation
   SmallVector<PHINode *, 4> SetjmpRetPHIs;
   Function *SetjmpF = M.getFunction("setjmp");
-  for (User *U : SetjmpF->users()) {
-    auto *CI = dyn_cast<CallInst>(U);
-    // FIXME 'invoke' to setjmp can happen when we use Wasm EH + Wasm SjLj, but
-    // we don't support two being used together yet.
-    if (!CI)
-      report_fatal_error("Wasm EH + Wasm SjLj is not fully supported yet");
-    BasicBlock *BB = CI->getParent();
+  for (auto *U : make_early_inc_range(SetjmpF->users())) {
+    auto *CB = dyn_cast<CallBase>(U);
+    BasicBlock *BB = CB->getParent();
     if (BB->getParent() != &F) // in other function
       continue;
 
+    CallInst *CI = nullptr;
+    // setjmp cannot throw. So if it is an invoke, lower it to a call
+    if (auto *II = dyn_cast<InvokeInst>(CB))
+      CI = llvm::changeToCall(II);
+    else
+      CI = cast<CallInst>(CB);
+
     // The tail is everything right after the call, and will be reached once
     // when setjmp is called, and later when longjmp returns to the setjmp
     BasicBlock *Tail = SplitBlock(BB, CI->getNextNode());
@@ -1596,6 +1640,13 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForEmscriptenSjLj(
     I->eraseFromParent();
 }
 
+static BasicBlock *getCleanupRetUnwindDest(const CleanupPadInst *CPI) {
+  for (const User *U : CPI->users())
+    if (const auto *CRI = dyn_cast<CleanupReturnInst>(U))
+      return CRI->getUnwindDest();
+  return nullptr;
+}
+
 // Create a catchpad in which we catch a longjmp's env and val arguments, test
 // if the longjmp corresponds to one of setjmps in the current function, and if
 // so, jump to the setjmp dispatch BB from which we go to one of post-setjmp
@@ -1747,6 +1798,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
       LongjmpableCalls.push_back(CI);
     }
   }
+
   for (auto *CI : LongjmpableCalls) {
     // Even if the callee function has attribute 'nounwind', which is true for
     // all C functions, it can longjmp, which means it can throw a Wasm
@@ -1754,8 +1806,58 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     CI->removeFnAttr(Attribute::NoUnwind);
     if (Function *CalleeF = CI->getCalledFunction())
       CalleeF->removeFnAttr(Attribute::NoUnwind);
+
     // Change it to an invoke and make it unwind to the catch.dispatch.longjmp
-    // BB.
-    changeToInvokeAndSplitBasicBlock(CI, CatchDispatchLongjmpBB);
+    // BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind
+    // to its parent pad's unwind destination instead to preserve the scope
+    // structure. It will eventually unwind to the catch.dispatch.longjmp.
+    SmallVector<OperandBundleDef, 1> Bundles;
+    BasicBlock *UnwindDest = nullptr;
+    if (auto Bundle = CI->getOperandBundle(LLVMContext::OB_funclet)) {
+      Instruction *FromPad = cast<Instruction>(Bundle->Inputs[0]);
+      while (!UnwindDest && FromPad) {
+        if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {
+          UnwindDest = CPI->getCatchSwitch()->getUnwindDest();
+          FromPad = nullptr; // stop searching
+        } else if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {
+          // getCleanupRetUnwindDest() can return nullptr when
+          // 1. This cleanuppad's matching cleanupret uwninds to caller
+          // 2. There is no matching cleanupret because it ends with
+          //    unreachable.
+          // In case of 2, we need to traverse the parent pad chain.
+          UnwindDest = getCleanupRetUnwindDest(CPI);
+          FromPad = cast<Instruction>(CPI->getParentPad());
+        }
+      }
+    }
+    if (!UnwindDest)
+      UnwindDest = CatchDispatchLongjmpBB;
+    changeToInvokeAndSplitBasicBlock(CI, UnwindDest);
+  }
+
+  SmallVector<Instruction *, 16> ToErase;
+  for (auto &BB : F) {
+    if (auto *CSI = dyn_cast<CatchSwitchInst>(BB.getFirstNonPHI())) {
+      if (CSI != CatchSwitchLongjmp && CSI->unwindsToCaller()) {
+        IRB.SetInsertPoint(CSI);
+        ToErase.push_back(CSI);
+        auto *NewCSI = IRB.CreateCatchSwitch(CSI->getParentPad(),
+                                             CatchDispatchLongjmpBB, 1);
+        NewCSI->addHandler(*CSI->handler_begin());
+        NewCSI->takeName(CSI);
+        CSI->replaceAllUsesWith(NewCSI);
+      }
+    }
+
+    if (auto *CRI = dyn_cast<CleanupReturnInst>(BB.getTerminator())) {
+      if (CRI->unwindsToCaller()) {
+        IRB.SetInsertPoint(CRI);
+        ToErase.push_back(CRI);
+        IRB.CreateCleanupRet(CRI->getCleanupPad(), CatchDispatchLongjmpBB);
+      }
+    }
   }
+
+  for (Instruction *I : ToErase)
+    I->eraseFromParent();
 }

diff  --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp
index 3ea82e10ba93d..9f33d2f82732b 100644
--- a/llvm/lib/Transforms/Utils/Local.cpp
+++ b/llvm/lib/Transforms/Utils/Local.cpp
@@ -2189,8 +2189,8 @@ CallInst *llvm::createCallMatchingInvoke(InvokeInst *II) {
   return NewCall;
 }
 
-/// changeToCall - Convert the specified invoke into a normal call.
-void llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
+// changeToCall - Convert the specified invoke into a normal call.
+CallInst *llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
   CallInst *NewCall = createCallMatchingInvoke(II);
   NewCall->takeName(II);
   NewCall->insertBefore(II);
@@ -2207,6 +2207,7 @@ void llvm::changeToCall(InvokeInst *II, DomTreeUpdater *DTU) {
   II->eraseFromParent();
   if (DTU)
     DTU->applyUpdates({{DominatorTree::Delete, BB, UnwindDestBB}});
+  return NewCall;
 }
 
 BasicBlock *llvm::changeToInvokeAndSplitBasicBlock(CallInst *CI,

diff  --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
new file mode 100644
index 0000000000000..ac18a683f67d3
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
@@ -0,0 +1,259 @@
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
+; RUN: llc < %s -wasm-enable-eh -wasm-enable-sjlj -exception-model=wasm -mattr=+exception-handling -verify-machineinstrs
+
+target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
+target triple = "wasm32-unknown-unknown"
+
+%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
+ at _ZL3buf = internal global [1 x %struct.__jmp_buf_tag] zeroinitializer, align 16
+
+; void test() {
+;   int jmpval = setjmp(buf);
+;   if (jmpval != 0)
+;     return;
+;   try {
+;     foo();
+;   } catch (...) {
+;   }
+; }
+define void @setjmp_and_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+; CHECK-LABEL: @setjmp_and_try(
+
+entry:
+  %call = call i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
+  %cmp = icmp ne i32 %call, 0
+  br i1 %cmp, label %return, label %if.end
+
+if.end:                                           ; preds = %entry
+  invoke void @foo()
+  to label %return unwind label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %if.end
+  %0 = catchswitch within none [label %catch.start] unwind to caller
+; CHECK:       catch.dispatch:
+; CHECK-NEXT:    catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
+
+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) #2 [ "funclet"(token %1) ]
+  call void @__cxa_end_catch() [ "funclet"(token %1) ]
+  catchret from %1 to label %return
+; CHECK:       catch.start:
+; CHECK:         [[T0:%.*]] = catchpad within {{.*}} [i8* null]
+; CHECK:         invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
+; CHECK-NEXT:    to label %.noexc unwind label %catch.dispatch.longjmp
+
+; CHECK:       .noexc:
+; CHECK-NEXT:     catchret from [[T0]] to label {{.*}}
+
+return:                                           ; preds = %catch.start, %if.end, %entry
+  ret void
+
+; CHECK:       catch.dispatch.longjmp:
+; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
+}
+
+; void setjmp_within_try() {
+;   try {
+;     foo();
+;     int jmpval = setjmp(buf);
+;     if (jmpval != 0)
+;       return;
+;     foo();
+;   } catch (...) {
+;   }
+; }
+define void @setjmp_within_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+; CHECK-LABEL: @setjmp_within_try(
+entry:
+  %jmpval = alloca i32, align 4
+  %exn.slot = alloca i8*, align 4
+  invoke void @foo()
+  to label %invoke.cont unwind label %catch.dispatch
+
+invoke.cont:                                      ; preds = %entry
+  %call = invoke i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
+  to label %invoke.cont1 unwind label %catch.dispatch
+
+invoke.cont1:                                     ; preds = %invoke.cont
+  store i32 %call, i32* %jmpval, align 4
+  %0 = load i32, i32* %jmpval, align 4
+  %cmp = icmp ne i32 %0, 0
+  br i1 %cmp, label %if.then, label %if.end
+
+if.then:                                          ; preds = %invoke.cont1
+  br label %try.cont
+
+if.end:                                           ; preds = %invoke.cont1
+  invoke void @foo()
+  to label %invoke.cont2 unwind label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %if.end, %invoke.cont, %entry
+  %1 = catchswitch within none [label %catch.start] unwind to caller
+
+; CHECK:       catch.dispatch:
+; CHECK:         catchswitch within none [label %catch.start] unwind label %catch.dispatch.longjmp
+catch.start:                                      ; preds = %catch.dispatch
+  %2 = catchpad within %1 [i8* null]
+  %3 = call i8* @llvm.wasm.get.exception(token %2)
+  store i8* %3, i8** %exn.slot, align 4
+  %4 = call i32 @llvm.wasm.get.ehselector(token %2)
+  br label %catch
+
+catch:                                            ; preds = %catch.start
+  %exn = load i8*, i8** %exn.slot, align 4
+  %5 = call i8* @__cxa_begin_catch(i8* %exn) #2 [ "funclet"(token %2) ]
+  call void @__cxa_end_catch() [ "funclet"(token %2) ]
+  catchret from %2 to label %catchret.dest
+; CHECK: catch:                                            ; preds = %catch.start
+; CHECK-NEXT:   %exn = load i8*, i8** %exn.slot15, align 4
+; CHECK-NEXT:   %5 = call i8* @__cxa_begin_catch(i8* %exn) #2 [ "funclet"(token %2) ]
+; CHECK-NEXT:   invoke void @__cxa_end_catch() [ "funclet"(token %2) ]
+; CHECK-NEXT:           to label %.noexc unwind label %catch.dispatch.longjmp
+
+catchret.dest:                                    ; preds = %catch
+  br label %try.cont
+
+try.cont:                                         ; preds = %invoke.cont2, %catchret.dest, %if.then
+  ret void
+
+invoke.cont2:                                     ; preds = %if.end
+  br label %try.cont
+
+; CHECK:       catch.dispatch.longjmp:
+; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
+}
+
+; void setjmp_and_nested_try() {
+;   int jmpval = setjmp(buf);
+;   if (jmpval != 0)
+;     return;
+;   try {
+;     foo();
+;     try {
+;       foo();
+;     } catch (...) {
+;       foo();
+;     }
+;   } catch (...) {
+;   }
+; }
+define void @setjmp_and_nested_try() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+; CHECK-LABEL: @setjmp_and_nested_try(
+entry:
+  %call = call i32 @setjmp(%struct.__jmp_buf_tag* getelementptr inbounds ([1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* @_ZL3buf, i32 0, i32 0)) #0
+  %cmp = icmp ne i32 %call, 0
+  br i1 %cmp, label %try.cont10, label %if.end
+
+if.end:                                           ; preds = %entry
+  invoke void @foo()
+  to label %invoke.cont unwind label %catch.dispatch5
+
+invoke.cont:                                      ; preds = %if.end
+  invoke void @foo()
+  to label %try.cont10 unwind label %catch.dispatch
+
+catch.dispatch:                                   ; preds = %invoke.cont
+  %0 = catchswitch within none [label %catch.start] unwind label %catch.dispatch5
+
+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) #2 [ "funclet"(token %1) ]
+  invoke void @foo() [ "funclet"(token %1) ]
+  to label %invoke.cont2 unwind label %ehcleanup
+
+invoke.cont2:                                     ; preds = %catch.start
+  invoke void @__cxa_end_catch() [ "funclet"(token %1) ]
+  to label %invoke.cont3 unwind label %catch.dispatch5
+
+invoke.cont3:                                     ; preds = %invoke.cont2
+  catchret from %1 to label %try.cont10
+
+ehcleanup:                                        ; preds = %catch.start
+  %5 = cleanuppad within %1 []
+  invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
+  to label %invoke.cont4 unwind label %terminate
+; CHECK:       ehcleanup:
+; CHECK-NEXT:    [[T0:%.*]] = cleanuppad within {{.*}} []
+; CHECK-NEXT:    invoke void @__cxa_end_catch() [ "funclet"(token [[T0]]) ]
+; CHECK-NEXT:    to label %invoke.cont4 unwind label %terminate
+
+invoke.cont4:                                     ; preds = %ehcleanup
+  cleanupret from %5 unwind label %catch.dispatch5
+; CHECK:       invoke.cont4:
+; CHECK-NEXT:    cleanupret from [[T0]] unwind label %catch.dispatch5
+
+catch.dispatch5:                                  ; preds = %invoke.cont4, %invoke.cont2, %catch.dispatch, %if.end
+  %6 = catchswitch within none [label %catch.start6] unwind to caller
+; CHECK:       catch.dispatch5:
+; CHECK-NEXT:    catchswitch within none [label %catch.start6] unwind label %catch.dispatch.longjmp
+
+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) #2 [ "funclet"(token %7) ]
+  call void @__cxa_end_catch() [ "funclet"(token %7) ]
+  catchret from %7 to label %try.cont10
+; CHECK:       catch.start6:
+; CHECK-NEXT:    [[T1:%.*]] = catchpad within {{.*}} [i8* null]
+; CHECK-NEXT:    call i8* @llvm.wasm.get.exception(token [[T1]])
+; CHECK-NEXT:    call i32 @llvm.wasm.get.ehselector(token [[T1]])
+; CHECK-NEXT:    call i8* @__cxa_begin_catch(i8* {{.*}}) {{.*}} [ "funclet"(token [[T1]]) ]
+; CHECK:         invoke void @__cxa_end_catch() [ "funclet"(token [[T1]]) ]
+; CHECK-NEXT:    to label %.noexc unwind label %catch.dispatch.longjmp
+
+; CHECK:       .noexc:
+; CHECK-NEXT:    catchret from [[T1]] to label {{.*}}
+
+try.cont10:                                       ; preds = %catch.start6, %invoke.cont3, %invoke.cont, %entry
+  ret void
+
+terminate:                                        ; preds = %ehcleanup
+  %11 = cleanuppad within %5 []
+  call void @terminate() #3 [ "funclet"(token %11) ]
+  unreachable
+; CHECK:       terminate:
+; CHECK-NEXT:    [[T2:%.*]] = cleanuppad within [[T0]] []
+; Note that this call unwinds not to %catch.dispatch.longjmp but to
+; %catch.dispatch5. This call is enclosed in the cleanuppad above, but there is
+; no matching catchret, which has the unwind destination. So this checks this
+; cleanuppad's parent, which is in 'ehcleanup', and unwinds to its unwind
+; destination, %catch.dispatch5.
+; This call was originally '_ZSt9terminatev', which is the mangled name for
+; 'std::terminate'. But we listed that as "cannot longjmp", we changed
+; the name of the function in this test to show the case in which a call has to
+; change to an invoke whose unwind destination is determined by its parent
+; chain.
+; CHECK-NEXT:    invoke void @terminate() {{.*}} [ "funclet"(token [[T2]]) ]
+; CHECK-NEXT:    to label %.noexc4 unwind label %catch.dispatch5
+
+; CHECK:       .noexc4:
+; CHECK-NEXT:    unreachable
+
+; CHECK:       catch.dispatch.longjmp:
+; CHECK-NEXT:    catchswitch within none [label %catch.longjmp] unwind to caller
+}
+
+declare void @foo()
+; Function Attrs: returns_twice
+declare i32 @setjmp(%struct.__jmp_buf_tag*) #0
+; Function Attrs: noreturn
+declare void @longjmp(%struct.__jmp_buf_tag*, i32) #1
+declare i32 @__gxx_wasm_personality_v0(...)
+; Function Attrs: nounwind
+declare i8* @llvm.wasm.get.exception(token) #2
+; Function Attrs: nounwind
+declare i32 @llvm.wasm.get.ehselector(token) #2
+declare i8* @__cxa_begin_catch(i8*)
+declare void @__cxa_end_catch()
+declare void @terminate()
+
+attributes #0 = { returns_twice }
+attributes #1 = { noreturn }
+attributes #2 = { nounwind }
+attributes #3 = { noreturn nounwind }


        


More information about the llvm-commits mailing list