Add exception handling (try/catch/finally) to an LLVM 22 IR frontend using invoke, landingpad, and the Itanium C++ ABI. Covers personality function, invoke vs call, catch dispatch, __cxa_begin/end_catch, resume, cleanup blocks, and Windows SEH notes.
72
87%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Use this skill when your language needs try/catch/finally semantics and you need to emit correct LLVM IR unwind tables.
Module, IRBuilder<>, and functions already emitting IR.LLVMCore.Every function that contains a landing pad must have a personality function attached.
#include "llvm/IR/Function.h"
#include "llvm/IR/IntrinsicsX86.h"
// Declare __gxx_personality_v0 (Itanium ABI — Linux/macOS)
llvm::FunctionType *PersonalityFT =
llvm::FunctionType::get(B.getInt32Ty(), /*vararg=*/true);
llvm::Function *Personality =
llvm::Function::Create(PersonalityFT,
llvm::Function::ExternalLinkage,
"__gxx_personality_v0", *M);
// Attach to every function that has landing pads
F->setPersonalityFn(Personality);call with invoke inside try blocksAny call that may throw must use invoke, not call:
llvm::BasicBlock *NormalBB = llvm::BasicBlock::Create(Ctx, "normal", F);
llvm::BasicBlock *LpadBB = llvm::BasicBlock::Create(Ctx, "lpad", F);
llvm::InvokeInst *II = B.CreateInvoke(
CalleeFT, // function type (FunctionType*)
Callee, // callee Value*
NormalBB, // branch on normal return
LpadBB, // branch on unwind
{Arg0, Arg1}, // call arguments
"result" // result name (omit for void)
);
// Normal path: result is available here
B.SetInsertPoint(NormalBB);
// ... use II as the return value ...The landing pad must be the first instruction in the unwind destination block:
B.SetInsertPoint(LpadBB);
// landingpad returns { ptr, i32 }
// ptr — exception object pointer
// i32 — selector (which clause matched)
llvm::Type *LPadTy = llvm::StructType::get(Ctx,
{B.getPtrTy(), B.getInt32Ty()});
llvm::LandingPadInst *LP = B.CreateLandingPad(LPadTy, /*numClauses=*/1);
// Add a catch clause for a specific type (typeinfo global pointer)
llvm::Constant *IntTypeInfo =
M->getOrInsertGlobal("_ZTIi", B.getPtrTy()); // typeinfo for int
LP->addClause(IntTypeInfo);
// OR: catch-all (like catch(...))
// LP->addClause(llvm::ConstantPointerNull::get(B.getPtrTy()));
// OR: cleanup — always run (destructors, finally)
// LP->setCleanup(true);
// Extract the two components
llvm::Value *ExnPtr = B.CreateExtractValue(LP, 0, "exn.ptr");
llvm::Value *SelVal = B.CreateExtractValue(LP, 1, "exn.sel");Use llvm.eh.typeid.for to get the expected selector value for a type:
#include "llvm/IR/Intrinsics.h"
llvm::Function *TypeIDFn = llvm::Intrinsic::getOrInsertDeclaration(
M, llvm::Intrinsic::eh_typeid_for, {B.getPtrTy()});
llvm::Value *ExpectedID = B.CreateCall(TypeIDFn, {IntTypeInfo});
llvm::Value *Matches = B.CreateICmpEQ(SelVal, ExpectedID);
llvm::BasicBlock *CatchBB = llvm::BasicBlock::Create(Ctx, "catch", F);
llvm::BasicBlock *RethrowBB = llvm::BasicBlock::Create(Ctx, "rethrow", F);
B.CreateCondBr(Matches, CatchBB, RethrowBB);__cxa_begin_catch / __cxa_end_catchThe Itanium ABI requires these around every catch handler:
// Declare once, reuse:
llvm::FunctionType *BeginTy =
llvm::FunctionType::get(B.getPtrTy(), {B.getPtrTy()}, false);
llvm::Function *BeginCatch =
llvm::Function::Create(BeginTy, llvm::Function::ExternalLinkage,
"__cxa_begin_catch", *M);
llvm::FunctionType *EndTy =
llvm::FunctionType::get(B.getVoidTy(), {}, false);
llvm::Function *EndCatch =
llvm::Function::Create(EndTy, llvm::Function::ExternalLinkage,
"__cxa_end_catch", *M);
// In the catch block:
B.SetInsertPoint(CatchBB);
llvm::Value *ExnObj = B.CreateCall(BeginCatch, {ExnPtr}, "exn.obj");
// ... handle the exception (load from ExnObj, etc.) ...
B.CreateCall(EndCatch, {});
B.CreateBr(AfterBB);resumeIf no handler matched, propagate the exception up:
B.SetInsertPoint(RethrowBB);
B.CreateResume(LP); // terminates the blockA cleanup landing pad runs regardless of exception type:
llvm::BasicBlock *CleanupBB = llvm::BasicBlock::Create(Ctx, "cleanup", F);
B.SetInsertPoint(CleanupBB);
llvm::LandingPadInst *CleanupLP = B.CreateLandingPad(LPadTy, 0);
CleanupLP->setCleanup(true);
// Run cleanup (call destructor, close file, etc.)
B.CreateCall(DestructorFn, {ObjPtr});
// Resume unwinding after cleanup
B.CreateResume(CleanupLP);// try { result = foo(x); }
// catch (int e) { result = -1; }
// Set personality once at function start
F->setPersonalityFn(Personality);
llvm::BasicBlock *TryBB = llvm::BasicBlock::Create(Ctx, "try", F);
llvm::BasicBlock *NormalBB = llvm::BasicBlock::Create(Ctx, "normal", F);
llvm::BasicBlock *LpadBB = llvm::BasicBlock::Create(Ctx, "lpad", F);
llvm::BasicBlock *CatchBB = llvm::BasicBlock::Create(Ctx, "catch", F);
llvm::BasicBlock *AfterBB = llvm::BasicBlock::Create(Ctx, "after", F);
// try block
B.SetInsertPoint(TryBB);
llvm::InvokeInst *II = B.CreateInvoke(FooFT, FooFn, NormalBB, LpadBB, {X});
// normal path
B.SetInsertPoint(NormalBB);
B.CreateBr(AfterBB);
// landing pad
B.SetInsertPoint(LpadBB);
llvm::LandingPadInst *LP = B.CreateLandingPad(LPadTy, 1);
LP->addClause(IntTypeInfo);
llvm::Value *ExnPtr = B.CreateExtractValue(LP, 0);
llvm::Value *SelVal = B.CreateExtractValue(LP, 1);
llvm::Value *IntTypeID = B.CreateCall(TypeIDFn, {IntTypeInfo});
B.CreateCondBr(B.CreateICmpEQ(SelVal, IntTypeID), CatchBB, RethrowBB);
// catch (int e)
B.SetInsertPoint(CatchBB);
B.CreateCall(BeginCatch, {ExnPtr});
llvm::Value *ResultCatch = B.getInt32(-1);
B.CreateCall(EndCatch, {});
B.CreateBr(AfterBB);
// rethrow
B.SetInsertPoint(RethrowBB);
B.CreateResume(LP);
// after — PHI for result
B.SetInsertPoint(AfterBB);
llvm::PHINode *Result = B.CreatePHI(B.getInt32Ty(), 2, "result");
Result->addIncoming(II, NormalBB);
Result->addIncoming(ResultCatch, CatchBB);For Windows x64, use __C_specific_handler as personality and the WinEH instruction set (catchswitch, catchpad, catchret, cleanuppad, cleanupret) — these are a different model from the Itanium landingpad. See LLVM WinEH docs.
call for throwable calls in functions with landing pads — use invoke.F->setPersonalityFn() — landing pads without a personality crash the backend.landingpad in a function that has no invoke — the verifier rejects it.__cxa_begin_catch / __cxa_end_catch around catch bodies (Itanium ABI).resume or unreachable.verifyModule(*M, &errs()) after wiring EH — terminator invariants are easy to violate.a251bde
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.