From ddc2c9ae59d75cea719d8a21da1e760b406c9443 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 20 Feb 2025 22:22:39 +0000 Subject: [PATCH 1/7] [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(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 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({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 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(U); - if (ThrowI->getFunction() != &F) + auto *ThrowI = dyn_cast(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 Date: Fri, 21 Feb 2025 01:41:01 +0000 Subject: [PATCH 2/7] 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({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 Date: Fri, 21 Feb 2025 01:45:51 +0000 Subject: [PATCH 3/7] 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 Ops; - Ops.push_back(getControlRoot()); // inchain for the terminator node const TargetLowering &TLI = DAG.getTargetLoweringInfo(); - Ops.push_back( + SmallVector 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({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 Date: Fri, 21 Feb 2025 02:11:36 +0000 Subject: [PATCH 4/7] 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 Ops = { + std::array 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 Ops; - Ops.push_back(getControlRoot()); // inchain for the terminator node const TargetLowering &TLI = DAG.getTargetLoweringInfo(); - Ops.push_back( + std::array 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({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 Date: Fri, 21 Feb 2025 23:11:44 +0000 Subject: [PATCH 5/7] 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 Date: Mon, 24 Feb 2025 23:57:16 -0800 Subject: [PATCH 6/7] Update llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp Co-authored-by: Derek Schuff --- 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(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(); From f8bd25e5fd12aba0f9603d52707f4bae9ada6ac8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 25 Feb 2025 07:59:06 +0000 Subject: [PATCH 7/7] 80 col wrapping --- 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 548ef7b7b4487..ea28f7262de54 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -3361,8 +3361,8 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) { LowerStatepoint(cast(I), EHPadBB); break; // wasm_throw, wasm_rethrow: This is usually done in visitTargetIntrinsic, - // but these intrinsics are special because they can be invoked, so we manually - // lower it to a DAG node here. + // 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(); std::array Ops = {