https://github.com/hmelder updated https://github.com/llvm/llvm-project/pull/183753
>From 38fb99534154f37f43a69318407550db2dbe45f5 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 10:42:47 +0000 Subject: [PATCH 01/13] [clang] Whitelist wasm when targeting GNUstep 2.x --- clang/lib/Driver/ToolChains/Clang.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index ab671d032644b..6f583af41999e 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -8091,7 +8091,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(); >From 6e135cdab416fb5c03ab3f78a61a575a1a6644ed Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 10:50:35 +0000 Subject: [PATCH 02/13] [CodeGen][ObjC] Mangle public symbols for wasm Emscripten requires that exported symbols. See https://github.com/emscripten-core/emscripten/pull/23563. --- clang/lib/CodeGen/CGObjCGNU.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 32fd6b760ed11..698fe16df7b85 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -179,8 +179,16 @@ class CGObjCGNU : public CGObjCRuntime { (R.getVersion() >= VersionTuple(major, minor)); } - std::string ManglePublicSymbol(StringRef Name) { - return (StringRef(CGM.getTriple().isOSBinFormatCOFF() ? "$_" : "._") + Name).str(); + const std::string ManglePublicSymbol(StringRef Name) { + StringRef prefix = "._"; + + // Exported symbols in Emscripten must be a valid Javascript identifier. + auto triple = CGM.getTriple(); + if (triple.isOSBinFormatCOFF() || triple.isOSBinFormatWasm()) { + prefix = "$_"; + } + + return (prefix + Name).str(); } std::string SymbolForProtocol(Twine Name) { >From 90ce962fd0b450eef7670fc2ad07165cff2a555a Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 21 Nov 2025 13:04:33 +0000 Subject: [PATCH 03/13] [CodeGen][ObjC] Fix class_registerAlias_np signature --- clang/lib/CodeGen/CGObjCGNU.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 698fe16df7b85..080b263e5fd4d 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -4119,8 +4119,7 @@ llvm::Function *CGObjCGNU::ModuleInitFunction() { if (!ClassAliases.empty()) { llvm::Type *ArgTypes[2] = {PtrTy, PtrToInt8Ty}; llvm::FunctionType *RegisterAliasTy = - llvm::FunctionType::get(Builder.getVoidTy(), - ArgTypes, false); + llvm::FunctionType::get(BoolTy, ArgTypes, false); llvm::Function *RegisterAlias = llvm::Function::Create( RegisterAliasTy, llvm::GlobalValue::ExternalWeakLinkage, "class_registerAlias_np", >From 2a9692a67daf099e97d10daa3c5e7c691717e81f Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 28 Nov 2025 16:28:35 +0000 Subject: [PATCH 04/13] [CodeGen][ObjC] Add WASM symbol mangling test --- .../CodeGenObjC/gnustep2-wasm32-symbols.m | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m b/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m new file mode 100644 index 0000000000000..7da73b8f1903e --- /dev/null +++ b/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -emit-llvm -fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s + +@class NSString; + +@protocol AProtocol +- (void) meth; +@end + +@interface AClass <AProtocol> +@end + +@implementation AClass +- (void) meth {} +@end + +// Make sure that all public symbols are mangled correctly. All exported symbols +// must be valid Javascript identifiers in Emscripten. +// CHECK: $"$_OBJC_PROTOCOL_AProtocol" = comdat any +// CHECK: @"$_OBJC_METACLASS_AClass" +// CHECK: @"$_OBJC_PROTOCOL_AProtocol" +// CHECK: @"$_OBJC_CLASS_AClass" +// CHECK: @"$_OBJC_REF_CLASS_AClass" +// CHECK: @"$_OBJC_INIT_CLASS_AClass" >From 912f59824d1b4ec4b96fbe85c62d8782e1f3d516 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Wed, 14 Jan 2026 10:23:18 +0000 Subject: [PATCH 05/13] [CodeGen][ObjC] Use C++-based EH for WASM targets The Wasm EH implementation in Clang pretty much hard-codes __gxx_wasm_personality_v0 by calling the veneer function _Unwind_CallPersonality instead of calling the personality function directly. While it is possible to remove _Unwind_CallPersonality and instead generate its body in CG, this will add a couple of instructions in each catch block. Doable, but we can also do the following: Since we already have C++-based EH for MinGW in CGObjCGNU, reusing it for Wasm saves us from implementing our own personality function and objc_begin_catch/objc_end_catch functions. --- clang/lib/CodeGen/CGException.cpp | 13 ++++++++++--- clang/lib/CodeGen/CGObjCGNU.cpp | 9 ++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index e9d20672ce185..749dde5a92dcd 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -161,6 +161,8 @@ static const EHPersonality &getObjCPersonality(const TargetInfo &Target, case ObjCRuntime::GNUstep: if (T.isOSCygMing()) return EHPersonality::GNU_CPlusPlus_SEH; + else if (T.isWasm()) + return EHPersonality::GNU_Wasm_CPlusPlus; else if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7)) return EHPersonality::GNUstep_ObjC; [[fallthrough]]; @@ -200,7 +202,8 @@ static const EHPersonality &getCXXPersonality(const TargetInfo &Target, static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target, const CodeGenOptions &CGOpts, const LangOptions &L) { - if (Target.getTriple().isWindowsMSVCEnvironment()) + auto Triple = Target.getTriple(); + if (Triple.isWindowsMSVCEnvironment()) return EHPersonality::MSVC_CxxFrameHandler3; switch (L.ObjCRuntime.getKind()) { @@ -218,8 +221,12 @@ static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target, return getObjCPersonality(Target, CGOpts, L); case ObjCRuntime::GNUstep: - return Target.getTriple().isOSCygMing() ? EHPersonality::GNU_CPlusPlus_SEH - : EHPersonality::GNU_ObjCXX; + if (Triple.isWasm()) + return EHPersonality::GNU_Wasm_CPlusPlus; + else if (Triple.isOSCygMing()) + return EHPersonality::GNU_CPlusPlus_SEH; + else + return EHPersonality::GNU_ObjCXX; // The GCC runtime's personality function inherently doesn't support // mixed EH. Use the ObjC personality just to avoid returning null. diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 080b263e5fd4d..f9835703a68a9 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -2368,12 +2368,11 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned runtimeABIVersion, MetaClassPtrAlias(nullptr), RuntimeVersion(runtimeABIVersion), ProtocolVersion(protocolClassVersion), ClassABIVersion(classABI) { + auto Triple = cgm.getContext().getTargetInfo().getTriple(); + msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend"); - usesSEHExceptions = - cgm.getContext().getTargetInfo().getTriple().isWindowsMSVCEnvironment(); - usesCxxExceptions = - cgm.getContext().getTargetInfo().getTriple().isOSCygMing() && - isRuntime(ObjCRuntime::GNUstep, 2); + usesSEHExceptions = Triple.isWindowsMSVCEnvironment(); + usesCxxExceptions = (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm(); CodeGenTypes &Types = CGM.getTypes(); IntTy = cast<llvm::IntegerType>( >From 21ce13271e3b275c30c27bbfa2892a407d49f021 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Mon, 26 Jan 2026 08:02:32 +0000 Subject: [PATCH 06/13] [Clang][CodeGen] Return dispatch block when popping catch scope --- clang/lib/CodeGen/CGException.cpp | 4 +++- clang/lib/CodeGen/CodeGenFunction.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index 749dde5a92dcd..0dbeed2b43d38 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1210,11 +1210,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/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 1073de1d25ec7..b44c8acda8488 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1304,7 +1304,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); >From 44ded3da5093ccc83a00e53b9845b186eff913f5 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Mon, 26 Jan 2026 10:48:09 +0000 Subject: [PATCH 07/13] [Clang] Create Wasm EH helper functions for ObjC EH --- clang/lib/CodeGen/CGException.cpp | 45 +++++++++++++++-------------- clang/lib/CodeGen/CodeGenFunction.h | 10 ++++++- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index 0dbeed2b43d38..dbf09bc102ce9 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1210,15 +1210,35 @@ static void emitCatchDispatchBlock(CodeGenFunction &CGF, } } -LLVM::BasicBlock *CodeGenFunction::popCatchScope() { +llvm::BasicBlock *CodeGenFunction::popCatchScope() { EHCatchScope &catchScope = cast<EHCatchScope>(*EHStack.begin()); - LLVM::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock(); + llvm::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock(); if (catchScope.hasEHBranches()) emitCatchDispatchBlock(*this, catchScope); EHStack.popCatch(); return dispatchBlock; } + +void CodeGenFunction::WasmEmitFallthroughRethrow(llvm::BasicBlock *WasmCatchStartBlock) { + assert(WasmCatchStartBlock); + // Navigate for the "rethrow" block. For CXX exceptions this was 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()); + Builder.SetInsertPoint(RethrowBlock); + llvm::Function *RethrowInCatchFn = + CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow); + EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {}); +} + void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) { unsigned NumHandlers = S.getNumHandlers(); EHCatchScope &CatchScope = cast<EHCatchScope>(*EHStack.begin()); @@ -1325,27 +1345,8 @@ void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) { Builder.CreateBr(ContBB); } - // Because in wasm we merge all catch clauses into one big catchpad, in case - // none of the types in catch handlers matches after we test against each of - // them, we should unwind to the next EH enclosing scope. We generate a call - // to rethrow function here to do that. if (EHPersonality::get(*this).isWasmPersonality() && !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()); - Builder.SetInsertPoint(RethrowBlock); - llvm::Function *RethrowInCatchFn = - CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow); - EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {}); + WasmEmitFallthroughRethrow(WasmCatchStartBlock); } EmitBlock(ContBB); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index b44c8acda8488..53e581cffc6ad 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1304,7 +1304,15 @@ 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). - LLVM::BasicBlock *popCatchScope(); + llvm::BasicBlock *popCatchScope(); + + // This function should be called after emitting all catch clauses and none + // of them were 'catch-all' clauses. + // Because in wasm we merge all catch clauses into one big catchpad, in case + // none of the types in catch handlers matches after we test against each of + // them, we should unwind to the next EH enclosing scope. We generate a call + // to rethrow function here to do that. + void WasmEmitFallthroughRethrow(llvm::BasicBlock *WasmCatchStartBlock); llvm::BasicBlock *getEHResumeBlock(bool isCleanup); llvm::BasicBlock *getEHDispatchBlock(EHScopeStack::stable_iterator scope); >From 925137e31004624c45cee178cc1104614bd75d3c Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Mon, 26 Jan 2026 10:49:09 +0000 Subject: [PATCH 08/13] [ObjC] Support Wasm EH --- clang/lib/CodeGen/CGObjCRuntime.cpp | 47 +++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 3d47dc9560c65..e33ad45fa061d 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "CGObjCRuntime.h" +#include "Address.h" #include "CGCXXABI.h" #include "CGCleanup.h" #include "CGRecordLayout.h" @@ -23,6 +24,7 @@ #include "clang/CodeGen/CGFunctionInfo.h" #include "clang/CodeGen/CodeGenABITypes.h" #include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" #include "llvm/Support/SaveAndRestore.h" using namespace clang; @@ -148,6 +150,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, Cont = CGF.getJumpDestInCurrentScope("eh.cont"); bool useFunclets = EHPersonality::get(CGF).usesFuncletPads(); + bool IsWasm = EHPersonality::get(CGF).isWasmPersonality(); + bool IsMSVC = EHPersonality::get(CGF).isMSVCPersonality(); CodeGenFunction::FinallyInfo FinallyInfo; if (!useFunclets) @@ -187,7 +191,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, Handlers[I].Block); } - if (useFunclets) + if (IsMSVC) if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) { CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true); if (!CGF.CurSEHParent) @@ -213,30 +217,57 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, CGF.EmitStmt(S.getTryBody()); // Leave the try. - if (S.getNumCatchStmts()) - CGF.popCatchScope(); + llvm::BasicBlock *DispatchBlock = nullptr; + if (S.getNumCatchStmts()) { + DispatchBlock = CGF.popCatchScope(); + } + + // TODO(hugo): Better documentation + // 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 *CPI = nullptr; + if (!!DispatchBlock && IsWasm) { + auto *CatchSwitch = + cast<llvm::CatchSwitchInst>(DispatchBlock->getFirstNonPHIIt()); + WasmCatchStartBlock = CatchSwitch->hasUnwindDest() + ? CatchSwitch->getSuccessor(1) + : CatchSwitch->getSuccessor(0); + CPI = + cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt()); + CGF.CurrentFuncletPad = CPI; + } // Remember where we were. CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP(); // Emit the handlers. + // TODO(hugo): Document + bool HasCatchAll = false; for (CatchHandler &Handler : Handlers) { + HasCatchAll |= Handler.TypeInfo == nullptr; CGF.EmitBlock(Handler.Block); CodeGenFunction::LexicalScope Cleanups(CGF, Handler.Body->getSourceRange()); SaveAndRestore RevertAfterScope(CGF.CurrentFuncletPad); - if (useFunclets) { + if (IsMSVC) { llvm::BasicBlock::iterator CPICandidate = Handler.Block->getFirstNonPHIIt(); if (CPICandidate != Handler.Block->end()) { - if (auto *CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate)) { + CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate); + if (!!CPI) { CGF.CurrentFuncletPad = CPI; CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF)); - CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI); } } } + if (!!CPI) { + CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI); + } + llvm::Value *RawExn = CGF.getExceptionFromSlot(); // Enter the catch. @@ -272,6 +303,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, CGF.EmitBranchThroughCleanup(Cont); } + if (IsWasm && !HasCatchAll) { + CGF.WasmEmitFallthroughRethrow(WasmCatchStartBlock); + } + // Go back to the try-statement fallthrough. CGF.Builder.restoreIP(SavedIP); >From afee02ea7d9ef2172a0409313e94c71eb32fb79c Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Mon, 26 Jan 2026 10:49:51 +0000 Subject: [PATCH 09/13] [ObjC][GNU] Use Cxx Exceptions on Wasm --- clang/lib/CodeGen/CGObjCGNU.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index f9835703a68a9..6a3ceb7d10e3e 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -2371,8 +2371,10 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned runtimeABIVersion, auto Triple = cgm.getContext().getTargetInfo().getTriple(); msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend"); + auto Triple = cgm.getContext().getTargetInfo().getTriple(); usesSEHExceptions = Triple.isWindowsMSVCEnvironment(); - usesCxxExceptions = (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm(); + usesCxxExceptions = (Triple.isOSCygMing() && + isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm(); CodeGenTypes &Types = CGM.getTypes(); IntTy = cast<llvm::IntegerType>( >From 8b556cc9e6e14b4edc2371c5ad0771b3cff9ba04 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 27 Feb 2026 11:35:51 +0000 Subject: [PATCH 10/13] [Clang][ObjC] Document EH codegen --- clang/lib/CodeGen/CGObjCRuntime.cpp | 53 +++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index e33ad45fa061d..a8d9d0750acf0 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -156,6 +156,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, CodeGenFunction::FinallyInfo FinallyInfo; if (!useFunclets) if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) + // The finally statement is executed as a cleanup for the normal and + // exceptional control flow out of a try-catch block. This is all + // implemented in FinallyInfo. Here we enter a new EHCatchScope. FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn, endCatchFn, exceptionRethrowFn); @@ -186,6 +189,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, Handler.TypeInfo = GetEHType(CatchDecl->getType()); } + // Create a new catch scope EHCatchScope *Catch = CGF.EHStack.pushCatch(Handlers.size()); for (unsigned I = 0, E = Handlers.size(); I != E; ++I) Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, Handlers[I].Block); @@ -216,16 +220,48 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, // Emit the try body. CGF.EmitStmt(S.getTryBody()); + // lpad or catch.dispatch (the dispatch block) has now been emitted + // + // Here an example: + // void may_throw(); + // @try { + // may_throw(); + // } @catch(id a) { + // } @catch(id b) { + // [...] + // + // With funclet-based exception handling, the dispatch block is created in + // getEHDispatchBlock() <- getInvokeDestImpl() <- EmitCall(). + // The following IR is emitted in this case: + // On aarch64-linux-gnu (landing-pad based) + // %call = invoke i32 @may_throw() + // to label %invoke.cont unwind label %lpad, !dbg !19 + // On aarch64-pc-windows-msvc (funclet based) + // %call = invoke i32 @may_throw() + // to label %invoke.cont unwind label %catch.dispatch, !dbg !17 + // Leave the try. llvm::BasicBlock *DispatchBlock = nullptr; if (S.getNumCatchStmts()) { + // The dispatch block that was created during the emission of the try block + // was cached. We retrieve it when popping the current catch scope. DispatchBlock = CGF.popCatchScope(); } - // TODO(hugo): Better documentation - // 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. + // On Windows and WASM, the new exception handling instructions are used. + // + // Continuing with the previous example, on Windows, we emit one catchpad for + // every catch handler. This is not the case for WASM where all catch handlers + // merged into one big catchpad: + // + // catch.dispatch: + // %0 = catchswitch within none [label %catch.start] unwind to caller + // catch.start: + // %1 = catchpad within %0 [ptr @__objc_id_type_info, ptr null] + // [...] + // br i1 %matches, label %catch, label %catch2 + // + // We save the old funclet pad here before we traverse each catch handler. SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad); llvm::BasicBlock *WasmCatchStartBlock = nullptr; llvm::CatchPadInst *CPI = nullptr; @@ -243,8 +279,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, // Remember where we were. CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP(); - // Emit the handlers. - // TODO(hugo): Document + // Emit the handlers. If there is no catch-all handler, we need to emit a + // fallthrough block in WASM. We therefore need to know if we have a + // catch-all handler in this catch scope. bool HasCatchAll = false; for (CatchHandler &Handler : Handlers) { HasCatchAll |= Handler.TypeInfo == nullptr; @@ -265,6 +302,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, } if (!!CPI) { + // A catchpad requires a matching catchret instruction. We emit this in + // form of a cleanup. CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI); } @@ -293,6 +332,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, EmitInitOfCatchParam(CGF, CastExn, CatchParam); } + // The body of the handler might have more try-catch blocks, so we need to + // save the current exception before emitting the body. CGF.ObjCEHValueStack.push_back(Exn); CGF.EmitStmt(Handler.Body); CGF.ObjCEHValueStack.pop_back(); >From 58928368c444993a654ff3ab93c093f3d247290e Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 27 Feb 2026 12:00:49 +0000 Subject: [PATCH 11/13] [CodeGen][ObjC] Disable @finally for WASM --- clang/lib/CodeGen/CGObjCRuntime.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index a8d9d0750acf0..76d7a80651604 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -25,6 +25,7 @@ #include "clang/CodeGen/CodeGenABITypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/SaveAndRestore.h" using namespace clang; @@ -154,13 +155,17 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, bool IsMSVC = EHPersonality::get(CGF).isMSVCPersonality(); CodeGenFunction::FinallyInfo FinallyInfo; - if (!useFunclets) - if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) + if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) { + if (!useFunclets) { // The finally statement is executed as a cleanup for the normal and // exceptional control flow out of a try-catch block. This is all // implemented in FinallyInfo. Here we enter a new EHCatchScope. - FinallyInfo.enter(CGF, Finally->getFinallyBody(), - beginCatchFn, endCatchFn, exceptionRethrowFn); + FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn, + endCatchFn, exceptionRethrowFn); + } else if (IsWasm) { + llvm_unreachable("@finally not implemented for WASM"); + } + } SmallVector<CatchHandler, 8> Handlers; >From f152b462b7d84823cf1b6a0a2164d1418ead6fa1 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 27 Feb 2026 13:57:28 +0000 Subject: [PATCH 12/13] [CodeGen][ObjC] Add WASM EH test case --- clang/test/CodeGenObjC/gnustep2-wasm32-eh.m | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m new file mode 100644 index 0000000000000..08aadfe507003 --- /dev/null +++ b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -fobjc-exceptions -fexceptions -exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm -fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s + +void may_throw(void) { + @throw (id) 1; +} + +int main(void) { + int retval = 0; + @try { + may_throw(); + // CHECK: invoke void @may_throw() + // CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]] + } + // Check that the dispatch block has been emitted correctly. + // CHECK: [[CATCH_DISPATCH]]: + // CHECK-NEXT: %[[CATCHSWITCH:.*]] = catchswitch within none [label %[[CATCH_START:.*]] unwind to caller + + + // The native WASM EH uses the new exception handling IR instructions + // (catchswitch, catchpad, etc.) that are also used when targeting Windows MSVC. + // For SEH, we emit a catchpad instruction for each catch statement. On WASM, we + // merge all catch statements into one big catch block. + + // CHECK: catchpad within %[[CATCHSWITCH]] [ptr @__objc_id_type_info, ptr null] + + // We use the cxa functions instead of objc_{begin,end}_catch. + // CHECK: call ptr @__cxa_begin_catch + @catch(id a) { + retval = 1; + } + @catch(...) { + retval = 2; + } + return retval; +} + + >From b3840f4f9b393cb1d591aea5d7eeba1079421744 Mon Sep 17 00:00:00 2001 From: hmelder <[email protected]> Date: Fri, 27 Feb 2026 15:41:15 +0000 Subject: [PATCH 13/13] [CodeGen][ObjC] Formatting --- clang/lib/CodeGen/CGException.cpp | 4 ++-- clang/lib/CodeGen/CGObjCGNU.cpp | 6 +++--- clang/lib/CodeGen/CGObjCRuntime.cpp | 18 ++++++++++-------- clang/lib/CodeGen/CodeGenFunction.h | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index dbf09bc102ce9..5df9d57ca9b45 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1219,8 +1219,8 @@ llvm::BasicBlock *CodeGenFunction::popCatchScope() { return dispatchBlock; } - -void CodeGenFunction::WasmEmitFallthroughRethrow(llvm::BasicBlock *WasmCatchStartBlock) { +void CodeGenFunction::WasmEmitFallthroughRethrow( + llvm::BasicBlock *WasmCatchStartBlock) { assert(WasmCatchStartBlock); // Navigate for the "rethrow" block. For CXX exceptions this was created in // emitWasmCatchPadBlock(). Wasm uses landingpad-style conditional branches diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp index 6a3ceb7d10e3e..20023070d273f 100644 --- a/clang/lib/CodeGen/CGObjCGNU.cpp +++ b/clang/lib/CodeGen/CGObjCGNU.cpp @@ -2371,10 +2371,10 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned runtimeABIVersion, auto Triple = cgm.getContext().getTargetInfo().getTriple(); msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend"); - auto Triple = cgm.getContext().getTargetInfo().getTriple(); usesSEHExceptions = Triple.isWindowsMSVCEnvironment(); - usesCxxExceptions = (Triple.isOSCygMing() && - isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm(); + usesCxxExceptions = + (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 2)) || + Triple.isWasm(); CodeGenTypes &Types = CGM.getTypes(); IntTy = cast<llvm::IntegerType>( diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp index 76d7a80651604..218d861673a0b 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -163,6 +163,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn, endCatchFn, exceptionRethrowFn); } else if (IsWasm) { + // dispatchBlock is finally.catchall + // emitWasmCatchPadBlock() + // CurrentFuncletPad = ... llvm_unreachable("@finally not implemented for WASM"); } } @@ -234,8 +237,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, // } @catch(id a) { // } @catch(id b) { // [...] - // - // With funclet-based exception handling, the dispatch block is created in + // + // With funclet-based exception handling, the dispatch block is created in // getEHDispatchBlock() <- getInvokeDestImpl() <- EmitCall(). // The following IR is emitted in this case: // On aarch64-linux-gnu (landing-pad based) @@ -248,13 +251,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, // Leave the try. llvm::BasicBlock *DispatchBlock = nullptr; if (S.getNumCatchStmts()) { - // The dispatch block that was created during the emission of the try block - // was cached. We retrieve it when popping the current catch scope. - DispatchBlock = CGF.popCatchScope(); + // The dispatch block that was created during the emission of the try block + // was cached. We retrieve it when popping the current catch scope. + DispatchBlock = CGF.popCatchScope(); } // On Windows and WASM, the new exception handling instructions are used. - // + // // Continuing with the previous example, on Windows, we emit one catchpad for // every catch handler. This is not the case for WASM where all catch handlers // merged into one big catchpad: @@ -276,8 +279,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, WasmCatchStartBlock = CatchSwitch->hasUnwindDest() ? CatchSwitch->getSuccessor(1) : CatchSwitch->getSuccessor(0); - CPI = - cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt()); + CPI = cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt()); CGF.CurrentFuncletPad = CPI; } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 53e581cffc6ad..c18e559f0333b 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1307,7 +1307,7 @@ class CodeGenFunction : public CodeGenTypeCache { llvm::BasicBlock *popCatchScope(); // This function should be called after emitting all catch clauses and none - // of them were 'catch-all' clauses. + // of them were 'catch-all' clauses. // Because in wasm we merge all catch clauses into one big catchpad, in case // none of the types in catch handlers matches after we test against each of // them, we should unwind to the next EH enclosing scope. We generate a call _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
