[llvm] [WebAssembly] Make llvm.wasm.throw invokable (PR #128104)

Heejin Ahn via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 24 23:57:26 PST 2025


https://github.com/aheejin updated https://github.com/llvm/llvm-project/pull/128104

>From ddc2c9ae59d75cea719d8a21da1e760b406c9443 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Thu, 20 Feb 2025 22:22:39 +0000
Subject: [PATCH 1/6] [WebAssembly] Make llvm.wasm.throw invokable

`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 #124710.
---
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 19 +++++++++++---
 llvm/lib/CodeGen/WasmEHPrepare.cpp            |  6 ++---
 llvm/lib/IR/Verifier.cpp                      |  4 ++-
 llvm/test/CodeGen/WebAssembly/exception.ll    | 26 +++++++++++++++++++
 4 files changed, 47 insertions(+), 8 deletions(-)

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)

>From 87456cc62a85ff78b9c44b0b7f8d8f61fdd47625 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Fri, 21 Feb 2025 01:41:01 +0000
Subject: [PATCH 2/6] clang-format insists this :(

---
 llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 2b333bd81a570..3f422737a4375 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3370,8 +3370,8 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
       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
+      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;

>From aa5fafda86059dded2dc5573ec160bbe0a786ba5 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Fri, 21 Feb 2025 01:45:51 +0000
Subject: [PATCH 3/6] Use a static array

---
 .../lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 3f422737a4375..c7cb6a70936d5 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3364,14 +3364,14 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
     // 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(
+      SmallVector<SDValue, 8> Ops = {
+          getControlRoot(), // inchain for the terminator node
           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
+                                TLI.getPointerTy(DAG.getDataLayout())),
+          getValue(I.getArgOperand(0)), // tag
+          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;

>From aa7b0f6c22348c72199d8366d1841772db4982c1 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Fri, 21 Feb 2025 02:11:36 +0000
Subject: [PATCH 4/6] Use std::array (also for wasm_rethrow)

---
 llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index c7cb6a70936d5..2172e396ed551 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3365,7 +3365,7 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
     // lower it to a DAG node here.
     case Intrinsic::wasm_throw: {
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();
-      SmallVector<SDValue, 8> Ops = {
+      std::array<SDValue, 4> Ops = {
           getControlRoot(), // inchain for the terminator node
           DAG.getTargetConstant(Intrinsic::wasm_throw, getCurSDLoc(),
                                 TLI.getPointerTy(DAG.getDataLayout())),
@@ -3377,12 +3377,11 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
       break;
     }
     case Intrinsic::wasm_rethrow: {
-      SmallVector<SDValue, 8> Ops;
-      Ops.push_back(getControlRoot()); // inchain for the terminator node
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();
-      Ops.push_back(
+      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()))};
       SDVTList VTs = DAG.getVTList(ArrayRef<EVT>({MVT::Other})); // outchain
       DAG.setRoot(DAG.getNode(ISD::INTRINSIC_VOID, getCurSDLoc(), VTs, Ops));
       break;

>From 614293e361113bfd2e1e5df2c6555821e0e2435c Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Fri, 21 Feb 2025 23:11:44 +0000
Subject: [PATCH 5/6] Error message update in test

---
 llvm/test/Verifier/invoke.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/Verifier/invoke.ll b/llvm/test/Verifier/invoke.ll
index 20f61b9041f21..11161e2618e03 100644
--- a/llvm/test/Verifier/invoke.ll
+++ b/llvm/test/Verifier/invoke.ll
@@ -46,7 +46,7 @@ contb:
 
 define i8 @f2() personality ptr @__gxx_personality_v0 {
 entry:
-; CHECK: Cannot invoke an intrinsic other than donothing, patchpoint, statepoint, coro_resume, coro_destroy or clang.arc.attachedcall
+; CHECK: Cannot invoke an intrinsic other than donothing, patchpoint, statepoint, coro_resume, coro_destroy, clang.arc.attachedcall or wasm.(re)throw
   invoke void @llvm.trap()
   to label %cont unwind label %lpad
 

>From 5d9f9484fa6fdb500928052ef28dbbc99fa6f6c5 Mon Sep 17 00:00:00 2001
From: Heejin Ahn <aheejin at gmail.com>
Date: Mon, 24 Feb 2025 23:57:16 -0800
Subject: [PATCH 6/6] Update
 llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp

Co-authored-by: Derek Schuff <dschuff at chromium.org>
---
 llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 2172e396ed551..548ef7b7b4487 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -3361,7 +3361,7 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) {
       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
+    // but these intrinsics are special because they can be invoked, so we manually
     // lower it to a DAG node here.
     case Intrinsic::wasm_throw: {
       const TargetLowering &TLI = DAG.getTargetLoweringInfo();



More information about the llvm-commits mailing list