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 <[email protected]> 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(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
