diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-13 14:52:57 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-13 14:52:57 +0000 |
| commit | a484f314b719aec5ed7bdc6f08403342b2125561 (patch) | |
| tree | 531e331cf823517eae4c350d37bca6a500b2de9f /lib/CodeGen/ExceptionHandler.cc | |
| parent | 4297086c86f4d11e8a14b76b50705b419d6b7f13 (diff) | |
feat(compiler)!: add try-catch with C++ stack unwinding
Add TRY/CATCH/ENDTRY and anonymous THROW statements. Use LLVM exception
handling and the C++ ABI to propagate exceptions across function calls,
clean up catch state, and handle uncaught exceptions at the program boundary.
Register exception runtime symbols for the statically linked embedded runner.
Verify runtime checks, nested catches, returns, loops, and invalid syntax
with smoke programs; exercise unwinding with MCJIT, lli, and native binaries.
BREAKING CHANGE: Runtime check failures now throw anonymous exceptions and
print only a generic message when uncaught. Native binaries require linking
the C++ runtime.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'lib/CodeGen/ExceptionHandler.cc')
| -rw-r--r-- | lib/CodeGen/ExceptionHandler.cc | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/CodeGen/ExceptionHandler.cc b/lib/CodeGen/ExceptionHandler.cc new file mode 100644 index 0000000..048f8a6 --- /dev/null +++ b/lib/CodeGen/ExceptionHandler.cc @@ -0,0 +1,128 @@ +#include "cps/ExceptionHandler.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Instructions.h" +#include <cassert> + +using namespace llvm; +using namespace cps; + +void ExceptionHandler::ensureRuntime() { + if (IntTypeInfo) return; + auto *Ptr = PointerType::getUnqual(Context); + auto *Void = Type::getVoidTy(Context); + Allocate = Module.getOrInsertFunction("__cxa_allocate_exception", + FunctionType::get(Ptr, {Type::getInt64Ty(Context)}, false)); + Throw = Module.getOrInsertFunction("__cxa_throw", + FunctionType::get(Void, {Ptr, Ptr, Ptr}, false)); + BeginCatch = Module.getOrInsertFunction("__cxa_begin_catch", + FunctionType::get(Ptr, {Ptr}, false)); + EndCatch = Module.getOrInsertFunction("__cxa_end_catch", + FunctionType::get(Void, false)); +#if defined(__SEH__) + const char *PersonalityName = "__gxx_personality_seh0"; +#elif defined(__USING_SJLJ_EXCEPTIONS__) + const char *PersonalityName = "__gxx_personality_sj0"; +#else + const char *PersonalityName = "__gxx_personality_v0"; +#endif + Personality = Module.getOrInsertFunction(PersonalityName, + FunctionType::get(Type::getInt32Ty(Context), true)); + IntTypeInfo = new GlobalVariable(Module, Ptr, true, GlobalValue::ExternalLinkage, + nullptr, "_ZTIi"); + cast<Function>(Allocate.getCallee())->setDoesNotThrow(); + cast<Function>(BeginCatch.getCallee())->setDoesNotThrow(); + // Only anonymous integer exceptions are thrown; there is no throwing + // exception-object destructor to invoke when a catch ends. + cast<Function>(EndCatch.getCallee())->setDoesNotThrow(); + cast<Function>(Throw.getCallee())->setDoesNotReturn(); +} + +void ExceptionHandler::prepareFunction(Function *F) { + F->setUWTableKind(UWTableKind::Default); +} + +void ExceptionHandler::pushHandler(BasicBlock *Target) { + auto *F = Builder.GetInsertBlock()->getParent(); + IRBuilder<> Entry(&F->getEntryBlock(), F->getEntryBlock().begin()); + auto *Exception = Entry.CreateAlloca(PointerType::getUnqual(Context), nullptr, + "caught_exception"); + auto &State = States[F]; + State.Handlers.push_back({Target, Exception, State.CatchDepth}); +} + +AllocaInst *ExceptionHandler::popHandler() { + auto &Handlers = States[Builder.GetInsertBlock()->getParent()].Handlers; + assert(!Handlers.empty()); + auto *Exception = Handlers.back().Exception; + Handlers.pop_back(); + return Exception; +} + +void ExceptionHandler::beginCatch(AllocaInst *Exception) { + ensureRuntime(); + auto *Ptr = Builder.CreateLoad(PointerType::getUnqual(Context), Exception); + Builder.CreateCall(BeginCatch, {Ptr}); + ++States[Builder.GetInsertBlock()->getParent()].CatchDepth; +} + +void ExceptionHandler::endCatch() { + auto &Depth = States[Builder.GetInsertBlock()->getParent()].CatchDepth; + assert(Depth != 0); + --Depth; + if (!Builder.GetInsertBlock()->getTerminator()) Builder.CreateCall(EndCatch); +} + +void ExceptionHandler::emitReturnCleanup() { + auto &State = States[Builder.GetInsertBlock()->getParent()]; + for (unsigned I = 0; I < State.CatchDepth; ++I) Builder.CreateCall(EndCatch); +} + +BasicBlock *ExceptionHandler::emitUnwindDestination() { + auto *F = Builder.GetInsertBlock()->getParent(); + auto &State = States[F]; + if (State.Handlers.empty() && State.CatchDepth == 0) return nullptr; + ensureRuntime(); + F->setPersonalityFn(cast<Constant>(Personality.getCallee())); + auto Saved = Builder.saveIP(); + auto *Unwind = BasicBlock::Create(Context, "exception_unwind", F); + Builder.SetInsertPoint(Unwind); + auto *LPType = StructType::get(PointerType::getUnqual(Context), Type::getInt32Ty(Context)); + auto *LP = Builder.CreateLandingPad(LPType, 1, "exception"); + const Handler *Handler = State.Handlers.empty() ? nullptr : &State.Handlers.back(); + unsigned TargetDepth = Handler ? Handler->CatchDepth : 0; + if (Handler) LP->addClause(ConstantPointerNull::get(PointerType::getUnqual(Context))); + if (State.CatchDepth > TargetDepth) LP->setCleanup(true); + // Finish catches we are leaving before entering the outer handler (or + // resuming propagation through a function with no local handler). + for (unsigned I = TargetDepth; I < State.CatchDepth; ++I) Builder.CreateCall(EndCatch); + if (Handler) { + Builder.CreateStore(Builder.CreateExtractValue(LP, 0), Handler->Exception); + Builder.CreateBr(Handler->Target); + } else { + Builder.CreateResume(LP); + } + Builder.restoreIP(Saved); + return Unwind; +} + +Value *ExceptionHandler::emitCall(FunctionCallee Callee, ArrayRef<Value *> Args, + const Twine &Name) { + auto *Unwind = emitUnwindDestination(); + if (!Unwind) return Builder.CreateCall(Callee, Args, Name); + auto *Cont = BasicBlock::Create(Context, "invoke_cont", Builder.GetInsertBlock()->getParent()); + auto *Call = Builder.CreateInvoke(Callee, Cont, Unwind, Args, Name); + Builder.SetInsertPoint(Cont); + return Call; +} + +void ExceptionHandler::emitThrow() { + ensureRuntime(); + // sizeof(int) is a target-folded null GEP, as for every other allocation. + auto *I32 = Type::getInt32Ty(Context); + auto *Null = ConstantPointerNull::get(PointerType::getUnqual(Context)); + auto *Size = ConstantExpr::getSizeOf(I32); + auto *Object = Builder.CreateCall(Allocate, {Size}, "anonymous_exception"); + Builder.CreateStore(ConstantInt::get(I32, 0), Object); + emitCall(Throw, {Object, IntTypeInfo, Null}); + Builder.CreateUnreachable(); +} |
