[clang] [CodeGen][ObjC] Try/Catch support for WebAssembly (PR #171038)
Hendrik Hübner via cfe-commits
cfe-commits at lists.llvm.org
Sun Dec 7 05:48:15 PST 2025
https://github.com/HendrikHuebner created https://github.com/llvm/llvm-project/pull/171038
Related to #169043 and part of a broader effort to support targetting WebAssembly for ObjectiveC.
This PR is work in progress and adds basic support for generating funclet-style exception handling (try/catch) for WebAssembly. For now, I've set the `gxx_wasm` personality function, however, we likely need to add a new one to handle ObjectiveC's `@finally` semantics.
>From 3a4b936d2e6ab1e1faf2d5803ab7ef2f037f78d6 Mon Sep 17 00:00:00 2001
From: hhuebner <hendrik.huebner18 at gmail.com>
Date: Sun, 7 Dec 2025 14:37:16 +0100
Subject: [PATCH] Try/Catch for ObjectiveC with WebAssembly target
---
clang/lib/CodeGen/CGCleanup.h | 1 +
clang/lib/CodeGen/CGException.cpp | 10 +++-
clang/lib/CodeGen/CGObjCRuntime.cpp | 66 +++++++++++++++++++++++----
clang/lib/CodeGen/CodeGenFunction.h | 2 +-
clang/lib/Driver/ToolChains/Clang.cpp | 3 +-
5 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index ba78e5478ac37..5f783216f8d0a 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -675,6 +675,7 @@ struct EHPersonality {
static const EHPersonality GNU_ObjC_SJLJ;
static const EHPersonality GNU_ObjC_SEH;
static const EHPersonality GNUstep_ObjC;
+ static const EHPersonality GNUstep_Wasm_ObjC;
static const EHPersonality GNU_ObjCXX;
static const EHPersonality NeXT_ObjC;
static const EHPersonality GNU_CPlusPlus;
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index f86af4581c345..2bb4ab8094965 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -159,9 +159,11 @@ static const EHPersonality &getObjCPersonality(const TargetInfo &Target,
case ObjCRuntime::WatchOS:
return EHPersonality::NeXT_ObjC;
case ObjCRuntime::GNUstep:
+ if (CGOpts.hasWasmExceptions())
+ return EHPersonality::GNU_Wasm_CPlusPlus;
if (T.isOSCygMing())
return EHPersonality::GNU_CPlusPlus_SEH;
- else if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
+ if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
return EHPersonality::GNUstep_ObjC;
[[fallthrough]];
case ObjCRuntime::GCC:
@@ -218,6 +220,8 @@ static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target,
return getObjCPersonality(Target, CGOpts, L);
case ObjCRuntime::GNUstep:
+ if (CGOpts.hasWasmExceptions())
+ return EHPersonality::GNU_Wasm_CPlusPlus;
return Target.getTriple().isOSCygMing() ? EHPersonality::GNU_CPlusPlus_SEH
: EHPersonality::GNU_ObjCXX;
@@ -1203,11 +1207,13 @@ static void emitCatchDispatchBlock(CodeGenFunction &CGF,
}
}
-void CodeGenFunction::popCatchScope() {
+llvm::BasicBlock *CodeGenFunction::popCatchScope() {
EHCatchScope &catchScope = cast<EHCatchScope>(*EHStack.begin());
+ llvm::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock();
if (catchScope.hasEHBranches())
emitCatchDispatchBlock(*this, catchScope);
EHStack.popCatch();
+ return dispatchBlock;
}
void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) {
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 76e0054f4c9da..aa46226956a22 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -23,6 +23,8 @@
#include "clang/CodeGen/CGFunctionInfo.h"
#include "clang/CodeGen/CodeGenABITypes.h"
#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
@@ -120,6 +122,8 @@ namespace {
llvm::Constant *TypeInfo;
/// Flags used to differentiate cleanups and catchalls in Windows SEH
unsigned Flags;
+
+ bool isCatchAll() const { return TypeInfo == nullptr; }
};
struct CallObjCEndCatch final : EHScopeStack::Cleanup {
@@ -148,13 +152,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
Cont = CGF.getJumpDestInCurrentScope("eh.cont");
bool useFunclets = EHPersonality::get(CGF).usesFuncletPads();
+ bool hasWasmExceptions = CGF.CGM.getCodeGenOpts().hasWasmExceptions();
CodeGenFunction::FinallyInfo FinallyInfo;
if (!useFunclets)
if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt())
FinallyInfo.enter(CGF, Finally->getFinallyBody(),
beginCatchFn, endCatchFn, exceptionRethrowFn);
-
SmallVector<CatchHandler, 8> Handlers;
@@ -187,8 +191,12 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, Handlers[I].Block);
}
- if (useFunclets)
+ if (useFunclets) {
if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
+ if (hasWasmExceptions) {
+ CGF.ErrorUnsupported(Finally, "@finally for WASM");
+ }
+
CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
if (!CGF.CurSEHParent)
CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
@@ -207,36 +215,59 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
// Push a cleanup for __finally blocks.
CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
}
+ }
// Emit the try body.
CGF.EmitStmt(S.getTryBody());
// Leave the try.
+ llvm::BasicBlock* dispatchBlock{};
if (S.getNumCatchStmts())
- CGF.popCatchScope();
+ dispatchBlock = CGF.popCatchScope();
// Remember where we were.
CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
+ // Wasm uses Windows-style EH instructions, but merges all catch clauses into
+ // one big catchpad. So we save the old funclet pad here before we traverse
+ // each catch handler.
+ SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad);
+ llvm::BasicBlock *WasmCatchStartBlock = nullptr;
+ llvm::CatchPadInst* CatchPadInst{};
+ if (!!dispatchBlock && hasWasmExceptions) {
+ auto *CatchSwitch =
+ cast<llvm::CatchSwitchInst>(dispatchBlock->getFirstNonPHIIt());
+ WasmCatchStartBlock = CatchSwitch->hasUnwindDest()
+ ? CatchSwitch->getSuccessor(1)
+ : CatchSwitch->getSuccessor(0);
+ CatchPadInst = cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt());
+ CGF.CurrentFuncletPad = CatchPadInst;
+ }
+
// Emit the handlers.
+ bool HasCatchAll = false;
for (CatchHandler &Handler : Handlers) {
+ HasCatchAll |= Handler.isCatchAll();
CGF.EmitBlock(Handler.Block);
CodeGenFunction::LexicalScope Cleanups(CGF, Handler.Body->getSourceRange());
SaveAndRestore RevertAfterScope(CGF.CurrentFuncletPad);
- if (useFunclets) {
+
+ if (useFunclets && !hasWasmExceptions) {
llvm::BasicBlock::iterator CPICandidate =
Handler.Block->getFirstNonPHIIt();
if (CPICandidate != Handler.Block->end()) {
- if (auto *CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate)) {
- CGF.CurrentFuncletPad = CPI;
- CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
- CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
+ if ((CatchPadInst = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate))) {
+ CGF.CurrentFuncletPad = CatchPadInst;
+ CatchPadInst->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
}
}
}
+ if (CatchPadInst)
+ CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CatchPadInst);
+
llvm::Value *RawExn = CGF.getExceptionFromSlot();
// Enter the catch.
@@ -272,6 +303,25 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
CGF.EmitBranchThroughCleanup(Cont);
}
+ if (!!dispatchBlock && hasWasmExceptions && !HasCatchAll) {
+ assert(WasmCatchStartBlock);
+ // Navigate for the "rethrow" block we created in emitWasmCatchPadBlock().
+ // Wasm uses landingpad-style conditional branches to compare selectors, so
+ // we follow the false destination for each of the cond branches to reach
+ // the rethrow block.
+ llvm::BasicBlock *RethrowBlock = WasmCatchStartBlock;
+ while (llvm::Instruction *TI = RethrowBlock->getTerminator()) {
+ auto *BI = cast<llvm::BranchInst>(TI);
+ assert(BI->isConditional());
+ RethrowBlock = BI->getSuccessor(1);
+ }
+ assert(RethrowBlock != WasmCatchStartBlock && RethrowBlock->empty());
+ CGF.Builder.SetInsertPoint(RethrowBlock);
+ llvm::Function *RethrowInCatchFn =
+ CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow);
+ CGF.EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {});
+ }
+
// Go back to the try-statement fallthrough.
CGF.Builder.restoreIP(SavedIP);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 8c4c1c8c2dc95..86c02f0ac6b2d 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1307,7 +1307,7 @@ class CodeGenFunction : public CodeGenTypeCache {
/// popCatchScope - Pops the catch scope at the top of the EHScope
/// stack, emitting any required code (other than the catch handlers
/// themselves).
- void popCatchScope();
+ llvm::BasicBlock* popCatchScope();
llvm::BasicBlock *getEHResumeBlock(bool isCleanup);
llvm::BasicBlock *getEHDispatchBlock(EHScopeStack::stable_iterator scope);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 2f0aec3ec3c37..1225789344c68 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8004,7 +8004,8 @@ ObjCRuntime Clang::AddObjCRuntimeArgs(const ArgList &args,
if ((runtime.getKind() == ObjCRuntime::GNUstep) &&
(runtime.getVersion() >= VersionTuple(2, 0)))
if (!getToolChain().getTriple().isOSBinFormatELF() &&
- !getToolChain().getTriple().isOSBinFormatCOFF()) {
+ !getToolChain().getTriple().isOSBinFormatCOFF() &&
+ !getToolChain().getTriple().isOSBinFormatWasm()) {
getToolChain().getDriver().Diag(
diag::err_drv_gnustep_objc_runtime_incompatible_binary)
<< runtime.getVersion().getMajor();
More information about the cfe-commits
mailing list