Skip to content

[WebAssembly] Make llvm.wasm.throw invokable #128104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 25, 2025
Merged

Conversation

aheejin
Copy link
Member

@aheejin aheejin commented Feb 21, 2025

llvm.wasm.throw intrinsic can throw but it was not invokable. Not sure what the rationale was when it was first written that way, but I think at least in Emscripten's C++ exception support with the Wasm port of libunwind, __builtin_wasm_throw, which is lowered down to llvm.wasm.rethrow, is used only within _Unwind_RaiseException, which is an one-liner and thus does not need an invoke: https://github.com/emscripten-core/emscripten/blob/720e97f76d6f19e0c6a2d6988988cfe23f0517fb/system/lib/libunwind/src/Unwind-wasm.c#L69 (_Unwind_RaiseException is called by __cxa_throw, which is generated by the throw C++ keyword)

But this does not address other direct uses of the builtin in C++, whose use I'm not sure about but is not prohibited. Also other language frontends may need to use the builtin in different functions, which has try-catches or destructors.

This makes llvm.wasm.throw invokable in the backend. To do that, this adds a custom lowering routine to SelectionDAGBuilder::visitInvoke, like we did for llvm.wasm.rethrow.

This does not generate invokes for __builtin_wasm_throw yet, which will be done by a follow-up PR.

Addresses #124710.

`llvm.wasm.throw` intrinsic can throw but it was not invokable. Not sure
what the rationale was when it was first written that way, but I think
at least in Emscripten's C++ exception support with the Wasm port of
libunwind, `__builtin_wasm_throw`, which is lowered down to
`llvm.wasm.rethrow`, is used only within `_Unwind_RaiseException`, which
is a one-liner and thus does not need an `invoke`:
https://github.com/emscripten-core/emscripten/blob/720e97f76d6f19e0c6a2d6988988cfe23f0517fb/system/lib/libunwind/src/Unwind-wasm.c#L69
(`_Unwind_RaiseException` is called by `__cxa_throw`, which is generated
by the `throw` C++ keyword)

But this does not address other direct uses of the builtin in C++, whose
use I'm not sure about but is not prohibited. Also other language
frontends may need to use the builtin in different functions, which has
`try`-`catch`es or destructors.

This makes `llvm.wasm.throw` invokable in the backend. To do that, this
adds a custom lowering routine to `SelectionDAGBuilder::visitInvoke`,
like we did for `llvm.wasm.rethrow`.

This does not generate `invoke`s for `__builtin_wasm_throw` yet, which
will be done by a follow-up PR.

Addresses llvm#124710.
@llvmbot
Copy link
Member

llvmbot commented Feb 21, 2025

@llvm/pr-subscribers-backend-webassembly

Author: Heejin Ahn (aheejin)

Changes

llvm.wasm.throw intrinsic can throw but it was not invokable. Not sure what the rationale was when it was first written that way, but I think at least in Emscripten's C++ exception support with the Wasm port of libunwind, __builtin_wasm_throw, which is lowered down to llvm.wasm.rethrow, is used only within _Unwind_RaiseException, which is a one-liner and thus does not need an invoke: https://github.com/emscripten-core/emscripten/blob/720e97f76d6f19e0c6a2d6988988cfe23f0517fb/system/lib/libunwind/src/Unwind-wasm.c#L69 (_Unwind_RaiseException is called by __cxa_throw, which is generated by the throw C++ keyword)

But this does not address other direct uses of the builtin in C++, whose use I'm not sure about but is not prohibited. Also other language frontends may need to use the builtin in different functions, which has try-catches or destructors.

This makes llvm.wasm.throw invokable in the backend. To do that, this adds a custom lowering routine to SelectionDAGBuilder::visitInvoke, like we did for llvm.wasm.rethrow.

This does not generate invokes for __builtin_wasm_throw yet, which will be done by a follow-up PR.

Addresses #124710.


Full diff: https://github.com/llvm/llvm-project/pull/128104.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+16-3)
  • (modified) llvm/lib/CodeGen/WasmEHPrepare.cpp (+2-4)
  • (modified) llvm/lib/IR/Verifier.cpp (+3-1)
  • (modified) llvm/test/CodeGen/WebAssembly/exception.ll (+26)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 1c58a7f05446c..2b333bd81a570 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3360,10 +3360,23 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
     case Intrinsic::experimental_gc_statepoint:
       LowerStatepoint(cast<GCStatepointInst>(I), EHPadBB);
       break;
+    // wasm_throw, wasm_rethrow: This is usually done in visitTargetIntrinsic,
+    // but this intrinsic is special because it can be invoked, so we manually
+    // lower it to a DAG node here.
+    case Intrinsic::wasm_throw: {
+      SmallVector<SDValue, 8> Ops;
+      Ops.push_back(getControlRoot()); // inchain for the terminator node
+      const TargetLowering &TLI = DAG.getTargetLoweringInfo();
+      Ops.push_back(
+          DAG.getTargetConstant(Intrinsic::wasm_throw, getCurSDLoc(),
+                                TLI.getPointerTy(DAG.getDataLayout())));
+      Ops.push_back(getValue(I.getArgOperand(0))); // tag
+      Ops.push_back(getValue(I.getArgOperand(1))); // thrown value
+      SDVTList VTs = DAG.getVTList(ArrayRef<EVT>({MVT::Other})); // outchain
+      DAG.setRoot(DAG.getNode(ISD::INTRINSIC_VOID, getCurSDLoc(), VTs, Ops));
+      break;
+    }
     case Intrinsic::wasm_rethrow: {
-      // This is usually done in visitTargetIntrinsic, but this intrinsic is
-      // special because it can be invoked, so we manually lower it to a DAG
-      // node here.
       SmallVector<SDValue, 8> Ops;
       Ops.push_back(getControlRoot()); // inchain for the terminator node
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();
diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp
index d18196b2217f5..fc98f594660bb 100644
--- a/llvm/lib/CodeGen/WasmEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp
@@ -201,10 +201,8 @@ bool WasmEHPrepareImpl::prepareThrows(Function &F) {
   // delete all following instructions within the BB, and delete all the dead
   // children of the BB as well.
   for (User *U : ThrowF->users()) {
-    // A call to @llvm.wasm.throw() is only generated from __cxa_throw()
-    // builtin call within libcxxabi, and cannot be an InvokeInst.
-    auto *ThrowI = cast<CallInst>(U);
-    if (ThrowI->getFunction() != &F)
+    auto *ThrowI = dyn_cast<CallInst>(U);
+    if (!ThrowI || ThrowI->getFunction() != &F)
       continue;
     Changed = true;
     auto *BB = ThrowI->getParent();
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 8432779c107de..0ef4438450ea2 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5203,10 +5203,12 @@ void Verifier::visitInstruction(Instruction &I) {
                 F->getIntrinsicID() == Intrinsic::experimental_patchpoint ||
                 F->getIntrinsicID() == Intrinsic::fake_use ||
                 F->getIntrinsicID() == Intrinsic::experimental_gc_statepoint ||
+                F->getIntrinsicID() == Intrinsic::wasm_throw ||
                 F->getIntrinsicID() == Intrinsic::wasm_rethrow ||
                 IsAttachedCallOperand(F, CBI, i),
             "Cannot invoke an intrinsic other than donothing, patchpoint, "
-            "statepoint, coro_resume, coro_destroy or clang.arc.attachedcall",
+            "statepoint, coro_resume, coro_destroy, clang.arc.attachedcall or "
+            "wasm.(re)throw",
             &I);
       Check(F->getParent() == &M, "Referencing function in another module!", &I,
             &M, F, F->getParent());
diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll
index febab822a6a9e..57d1f37c0039f 100644
--- a/llvm/test/CodeGen/WebAssembly/exception.ll
+++ b/llvm/test/CodeGen/WebAssembly/exception.ll
@@ -566,6 +566,32 @@ unreachable:                                      ; preds = %entry
   unreachable
 }
 
+; This tests whether llvm.wasm.throw intrinsic can invoked and iseled correctly.
+
+; CHECK-LABEL: invoke_throw:
+; CHECK: try_table    (catch __cpp_exception 0)
+; CHECK:   local.get  0
+; CHECK:   throw     __cpp_exception
+; CHECK: end_try_table
+define void @invoke_throw(ptr %p) personality ptr @__gxx_wasm_personality_v0 {
+entry:
+  invoke void @llvm.wasm.throw(i32 0, ptr %p)
+          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 [ptr null]
+  %2 = call ptr @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  %4 = call ptr @__cxa_begin_catch(ptr %2) #4 [ "funclet"(token %1) ]
+  call void @__cxa_end_catch() [ "funclet"(token %1) ]
+  catchret from %1 to label %try.cont
+
+try.cont:                                         ; preds = %catch, %entry
+  ret void
+}
 
 declare void @foo()
 declare void @bar(ptr)

@llvmbot
Copy link
Member

llvmbot commented Feb 21, 2025

@llvm/pr-subscribers-llvm-ir

Author: Heejin Ahn (aheejin)

Changes

llvm.wasm.throw intrinsic can throw but it was not invokable. Not sure what the rationale was when it was first written that way, but I think at least in Emscripten's C++ exception support with the Wasm port of libunwind, __builtin_wasm_throw, which is lowered down to llvm.wasm.rethrow, is used only within _Unwind_RaiseException, which is a one-liner and thus does not need an invoke: https://github.com/emscripten-core/emscripten/blob/720e97f76d6f19e0c6a2d6988988cfe23f0517fb/system/lib/libunwind/src/Unwind-wasm.c#L69 (_Unwind_RaiseException is called by __cxa_throw, which is generated by the throw C++ keyword)

But this does not address other direct uses of the builtin in C++, whose use I'm not sure about but is not prohibited. Also other language frontends may need to use the builtin in different functions, which has try-catches or destructors.

This makes llvm.wasm.throw invokable in the backend. To do that, this adds a custom lowering routine to SelectionDAGBuilder::visitInvoke, like we did for llvm.wasm.rethrow.

This does not generate invokes for __builtin_wasm_throw yet, which will be done by a follow-up PR.

Addresses #124710.


Full diff: https://github.com/llvm/llvm-project/pull/128104.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+16-3)
  • (modified) llvm/lib/CodeGen/WasmEHPrepare.cpp (+2-4)
  • (modified) llvm/lib/IR/Verifier.cpp (+3-1)
  • (modified) llvm/test/CodeGen/WebAssembly/exception.ll (+26)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 1c58a7f05446c..2b333bd81a570 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3360,10 +3360,23 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
     case Intrinsic::experimental_gc_statepoint:
       LowerStatepoint(cast<GCStatepointInst>(I), EHPadBB);
       break;
+    // wasm_throw, wasm_rethrow: This is usually done in visitTargetIntrinsic,
+    // but this intrinsic is special because it can be invoked, so we manually
+    // lower it to a DAG node here.
+    case Intrinsic::wasm_throw: {
+      SmallVector<SDValue, 8> Ops;
+      Ops.push_back(getControlRoot()); // inchain for the terminator node
+      const TargetLowering &TLI = DAG.getTargetLoweringInfo();
+      Ops.push_back(
+          DAG.getTargetConstant(Intrinsic::wasm_throw, getCurSDLoc(),
+                                TLI.getPointerTy(DAG.getDataLayout())));
+      Ops.push_back(getValue(I.getArgOperand(0))); // tag
+      Ops.push_back(getValue(I.getArgOperand(1))); // thrown value
+      SDVTList VTs = DAG.getVTList(ArrayRef<EVT>({MVT::Other})); // outchain
+      DAG.setRoot(DAG.getNode(ISD::INTRINSIC_VOID, getCurSDLoc(), VTs, Ops));
+      break;
+    }
     case Intrinsic::wasm_rethrow: {
-      // This is usually done in visitTargetIntrinsic, but this intrinsic is
-      // special because it can be invoked, so we manually lower it to a DAG
-      // node here.
       SmallVector<SDValue, 8> Ops;
       Ops.push_back(getControlRoot()); // inchain for the terminator node
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();
diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp
index d18196b2217f5..fc98f594660bb 100644
--- a/llvm/lib/CodeGen/WasmEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp
@@ -201,10 +201,8 @@ bool WasmEHPrepareImpl::prepareThrows(Function &F) {
   // delete all following instructions within the BB, and delete all the dead
   // children of the BB as well.
   for (User *U : ThrowF->users()) {
-    // A call to @llvm.wasm.throw() is only generated from __cxa_throw()
-    // builtin call within libcxxabi, and cannot be an InvokeInst.
-    auto *ThrowI = cast<CallInst>(U);
-    if (ThrowI->getFunction() != &F)
+    auto *ThrowI = dyn_cast<CallInst>(U);
+    if (!ThrowI || ThrowI->getFunction() != &F)
       continue;
     Changed = true;
     auto *BB = ThrowI->getParent();
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 8432779c107de..0ef4438450ea2 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5203,10 +5203,12 @@ void Verifier::visitInstruction(Instruction &I) {
                 F->getIntrinsicID() == Intrinsic::experimental_patchpoint ||
                 F->getIntrinsicID() == Intrinsic::fake_use ||
                 F->getIntrinsicID() == Intrinsic::experimental_gc_statepoint ||
+                F->getIntrinsicID() == Intrinsic::wasm_throw ||
                 F->getIntrinsicID() == Intrinsic::wasm_rethrow ||
                 IsAttachedCallOperand(F, CBI, i),
             "Cannot invoke an intrinsic other than donothing, patchpoint, "
-            "statepoint, coro_resume, coro_destroy or clang.arc.attachedcall",
+            "statepoint, coro_resume, coro_destroy, clang.arc.attachedcall or "
+            "wasm.(re)throw",
             &I);
       Check(F->getParent() == &M, "Referencing function in another module!", &I,
             &M, F, F->getParent());
diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll
index febab822a6a9e..57d1f37c0039f 100644
--- a/llvm/test/CodeGen/WebAssembly/exception.ll
+++ b/llvm/test/CodeGen/WebAssembly/exception.ll
@@ -566,6 +566,32 @@ unreachable:                                      ; preds = %entry
   unreachable
 }
 
+; This tests whether llvm.wasm.throw intrinsic can invoked and iseled correctly.
+
+; CHECK-LABEL: invoke_throw:
+; CHECK: try_table    (catch __cpp_exception 0)
+; CHECK:   local.get  0
+; CHECK:   throw     __cpp_exception
+; CHECK: end_try_table
+define void @invoke_throw(ptr %p) personality ptr @__gxx_wasm_personality_v0 {
+entry:
+  invoke void @llvm.wasm.throw(i32 0, ptr %p)
+          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 [ptr null]
+  %2 = call ptr @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  %4 = call ptr @__cxa_begin_catch(ptr %2) #4 [ "funclet"(token %1) ]
+  call void @__cxa_end_catch() [ "funclet"(token %1) ]
+  catchret from %1 to label %try.cont
+
+try.cont:                                         ; preds = %catch, %entry
+  ret void
+}
 
 declare void @foo()
 declare void @bar(ptr)

@llvmbot
Copy link
Member

llvmbot commented Feb 21, 2025

@llvm/pr-subscribers-llvm-selectiondag

Author: Heejin Ahn (aheejin)

Changes

llvm.wasm.throw intrinsic can throw but it was not invokable. Not sure what the rationale was when it was first written that way, but I think at least in Emscripten's C++ exception support with the Wasm port of libunwind, __builtin_wasm_throw, which is lowered down to llvm.wasm.rethrow, is used only within _Unwind_RaiseException, which is a one-liner and thus does not need an invoke: https://github.com/emscripten-core/emscripten/blob/720e97f76d6f19e0c6a2d6988988cfe23f0517fb/system/lib/libunwind/src/Unwind-wasm.c#L69 (_Unwind_RaiseException is called by __cxa_throw, which is generated by the throw C++ keyword)

But this does not address other direct uses of the builtin in C++, whose use I'm not sure about but is not prohibited. Also other language frontends may need to use the builtin in different functions, which has try-catches or destructors.

This makes llvm.wasm.throw invokable in the backend. To do that, this adds a custom lowering routine to SelectionDAGBuilder::visitInvoke, like we did for llvm.wasm.rethrow.

This does not generate invokes for __builtin_wasm_throw yet, which will be done by a follow-up PR.

Addresses #124710.


Full diff: https://github.com/llvm/llvm-project/pull/128104.diff

4 Files Affected:

  • (modified) llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp (+16-3)
  • (modified) llvm/lib/CodeGen/WasmEHPrepare.cpp (+2-4)
  • (modified) llvm/lib/IR/Verifier.cpp (+3-1)
  • (modified) llvm/test/CodeGen/WebAssembly/exception.ll (+26)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 1c58a7f05446c..2b333bd81a570 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3360,10 +3360,23 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
     case Intrinsic::experimental_gc_statepoint:
       LowerStatepoint(cast<GCStatepointInst>(I), EHPadBB);
       break;
+    // wasm_throw, wasm_rethrow: This is usually done in visitTargetIntrinsic,
+    // but this intrinsic is special because it can be invoked, so we manually
+    // lower it to a DAG node here.
+    case Intrinsic::wasm_throw: {
+      SmallVector<SDValue, 8> Ops;
+      Ops.push_back(getControlRoot()); // inchain for the terminator node
+      const TargetLowering &TLI = DAG.getTargetLoweringInfo();
+      Ops.push_back(
+          DAG.getTargetConstant(Intrinsic::wasm_throw, getCurSDLoc(),
+                                TLI.getPointerTy(DAG.getDataLayout())));
+      Ops.push_back(getValue(I.getArgOperand(0))); // tag
+      Ops.push_back(getValue(I.getArgOperand(1))); // thrown value
+      SDVTList VTs = DAG.getVTList(ArrayRef<EVT>({MVT::Other})); // outchain
+      DAG.setRoot(DAG.getNode(ISD::INTRINSIC_VOID, getCurSDLoc(), VTs, Ops));
+      break;
+    }
     case Intrinsic::wasm_rethrow: {
-      // This is usually done in visitTargetIntrinsic, but this intrinsic is
-      // special because it can be invoked, so we manually lower it to a DAG
-      // node here.
       SmallVector<SDValue, 8> Ops;
       Ops.push_back(getControlRoot()); // inchain for the terminator node
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();
diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp
index d18196b2217f5..fc98f594660bb 100644
--- a/llvm/lib/CodeGen/WasmEHPrepare.cpp
+++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp
@@ -201,10 +201,8 @@ bool WasmEHPrepareImpl::prepareThrows(Function &F) {
   // delete all following instructions within the BB, and delete all the dead
   // children of the BB as well.
   for (User *U : ThrowF->users()) {
-    // A call to @llvm.wasm.throw() is only generated from __cxa_throw()
-    // builtin call within libcxxabi, and cannot be an InvokeInst.
-    auto *ThrowI = cast<CallInst>(U);
-    if (ThrowI->getFunction() != &F)
+    auto *ThrowI = dyn_cast<CallInst>(U);
+    if (!ThrowI || ThrowI->getFunction() != &F)
       continue;
     Changed = true;
     auto *BB = ThrowI->getParent();
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 8432779c107de..0ef4438450ea2 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5203,10 +5203,12 @@ void Verifier::visitInstruction(Instruction &I) {
                 F->getIntrinsicID() == Intrinsic::experimental_patchpoint ||
                 F->getIntrinsicID() == Intrinsic::fake_use ||
                 F->getIntrinsicID() == Intrinsic::experimental_gc_statepoint ||
+                F->getIntrinsicID() == Intrinsic::wasm_throw ||
                 F->getIntrinsicID() == Intrinsic::wasm_rethrow ||
                 IsAttachedCallOperand(F, CBI, i),
             "Cannot invoke an intrinsic other than donothing, patchpoint, "
-            "statepoint, coro_resume, coro_destroy or clang.arc.attachedcall",
+            "statepoint, coro_resume, coro_destroy, clang.arc.attachedcall or "
+            "wasm.(re)throw",
             &I);
       Check(F->getParent() == &M, "Referencing function in another module!", &I,
             &M, F, F->getParent());
diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll
index febab822a6a9e..57d1f37c0039f 100644
--- a/llvm/test/CodeGen/WebAssembly/exception.ll
+++ b/llvm/test/CodeGen/WebAssembly/exception.ll
@@ -566,6 +566,32 @@ unreachable:                                      ; preds = %entry
   unreachable
 }
 
+; This tests whether llvm.wasm.throw intrinsic can invoked and iseled correctly.
+
+; CHECK-LABEL: invoke_throw:
+; CHECK: try_table    (catch __cpp_exception 0)
+; CHECK:   local.get  0
+; CHECK:   throw     __cpp_exception
+; CHECK: end_try_table
+define void @invoke_throw(ptr %p) personality ptr @__gxx_wasm_personality_v0 {
+entry:
+  invoke void @llvm.wasm.throw(i32 0, ptr %p)
+          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 [ptr null]
+  %2 = call ptr @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  %4 = call ptr @__cxa_begin_catch(ptr %2) #4 [ "funclet"(token %1) ]
+  call void @__cxa_end_catch() [ "funclet"(token %1) ]
+  catchret from %1 to label %try.cont
+
+try.cont:                                         ; preds = %catch, %entry
+  ret void
+}
 
 declare void @foo()
 declare void @bar(ptr)

aheejin added a commit to aheejin/llvm-project that referenced this pull request Feb 21, 2025
Even though `__builtin_wasm_throw`, which is lowered down to
`llvm.wasm.throw`, throws,
```cpp
try {
  __builtin_wasm_throw(0, obj);
} catch (...) {
}
```
does not generate `invoke`. This is because we have assumed the
intrinsic cannot be invoked, which doesn't make much sense. (See llvm#128104
for the historical context)

 llvm#128104 made `llvm.wasm.throw` intrinsic invokable in the backend. This
actually generates `invoke`s in Clang for `__builtin_wasm_throw`.

While we're at it, this also generates `invoke`s for
`__builtin_wasm_rethrow`, which is actually not used anywhere in C++
support. I haven't deleted it just in case in may have uses later. (For
example, to support rethrow functionality that carries stack trace
with exnref)

Depends on llvm#128104 for the CI to pass.
Fixes llvm#124710.
Copy link

github-actions bot commented Feb 21, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Comment on lines 3367 to 3374
SmallVector<SDValue, 8> Ops;
Ops.push_back(getControlRoot()); // inchain for the terminator node
const TargetLowering &TLI = DAG.getTargetLoweringInfo();
Ops.push_back(
DAG.getTargetConstant(Intrinsic::wasm_throw, getCurSDLoc(),
TLI.getPointerTy(DAG.getDataLayout())));
Ops.push_back(getValue(I.getArgOperand(0))); // tag
Ops.push_back(getValue(I.getArgOperand(1))); // thrown value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can replace opts with a static sized, initialized array

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: aa5fafd

const TargetLowering &TLI = DAG.getTargetLoweringInfo();
Ops.push_back(
SmallVector<SDValue, 8> Ops = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::array

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: aa7b0f6

Comment on lines +3381 to +3384
std::array<SDValue, 2> Ops = {
getControlRoot(), // inchain for the terminator node
DAG.getTargetConstant(Intrinsic::wasm_rethrow, getCurSDLoc(),
TLI.getPointerTy(DAG.getDataLayout())));
TLI.getPointerTy(DAG.getDataLayout()))};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just makes the existing wasm_rethrow use std::array and NFC

@aheejin aheejin merged commit d2d469e into llvm:main Feb 25, 2025
11 checks passed
@aheejin aheejin deleted the invokable_throw branch February 25, 2025 17:53
aheejin added a commit that referenced this pull request Feb 25, 2025
Even though `__builtin_wasm_throw`, which is lowered down to
`llvm.wasm.throw`, throws,
```cpp
try {
  __builtin_wasm_throw(0, obj);
} catch (...) {
}
```
does not generate `invoke`. This is because we have assumed the
intrinsic cannot be invoked, which doesn't make much sense. (See #128104
for the historical context)

#128104 made `llvm.wasm.throw` intrinsic invokable in the backend. This
actually generates `invoke`s in Clang for `__builtin_wasm_throw`.

While we're at it, this also generates `invoke`s for
`__builtin_wasm_rethrow`, which is actually not used anywhere in C++
support. I haven't deleted it just in case in may have uses later. (For
example, to support rethrow functionality that carries stack trace with
exnref)

Depends on #128104 for the CI to pass.
Fixes #124710.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants