LLVM 22.x tile for building compilers, language runtimes, and out-of-tree tooling
88
83%
Does it follow best practices?
Impact
96%
1.23xAverage score across 5 eval scenarios
Passed
No known issues
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.docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
skills
add-alias-analysis
add-attributes-metadata
add-calling-convention
add-debug-info
add-exception-handling
add-gc-statepoints
add-intrinsic
add-lto
add-sanitizer
add-vectorization-hint
frontend-to-ir
jit-setup
lit-filecheck
lower-struct-types
new-target
out-of-tree-setup
tessl-llvm
version-sync