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 | |
| 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')
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 76 | ||||
| -rw-r--r-- | lib/CodeGen/ExceptionHandler.cc | 128 | ||||
| -rw-r--r-- | lib/CodeGen/FileHandler.cc | 51 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 17 | ||||
| -rw-r--r-- | lib/CodeGen/RuntimeCheck.cc | 59 |
5 files changed, 232 insertions, 99 deletions
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index dcf665a..f69fc03 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -2,6 +2,7 @@ #include "cps/ArrayHandler.h" #include "cps/FileAST.h" #include "cps/FileHandler.h" +#include "cps/ExceptionHandler.h" #include "cps/Lexer.h" #include "cps/TypeAST.h" #include "llvm/IR/Instructions.h" @@ -69,7 +70,8 @@ CodeGen::CodeGen() { Builder = std::make_unique<IRBuilder<>>(*TheContext); Types = std::make_unique<TypeSystem>(*TheContext); - RuntimeChecker = std::make_unique<RuntimeCheck>(*TheModule, *TheContext, *Builder); + Exceptions = std::make_unique<ExceptionHandler>(*TheContext, *Builder, *TheModule); + RuntimeChecker = std::make_unique<RuntimeCheck>(*TheContext, *Builder, *Exceptions); Arrays = std::make_unique<ArrayHandler>(*TheContext, *Builder, @@ -82,6 +84,7 @@ CodeGen::CodeGen() { *TheModule, *Builder, *Types, + *Exceptions, Symbols, HadError); @@ -89,7 +92,8 @@ CodeGen::CodeGen() { *RuntimeChecker); StrHandler = std::make_unique<StringHandler>(*TheContext, *Builder, *TheModule); StrConvHandler = std::make_unique<StringConversionHandler>(*TheContext, *Builder, *TheModule); - Files = std::make_unique<FileHandler>(*TheContext, *Builder, *TheModule, *RuntimeChecker); + Files = std::make_unique<FileHandler>(*TheContext, *Builder, *TheModule, *RuntimeChecker, + *Exceptions); // Array parameters are bound into ArrayHandler through this inversion so // FunctionGen never sees the array subsystem. @@ -517,6 +521,10 @@ void CodeGen::compile(const std::vector<std::unique_ptr<StmtAST>> &Statements) { BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", F); Builder->SetInsertPoint(BB); + Exceptions->prepareFunction(F); + BasicBlock *UnhandledBB = BasicBlock::Create(*TheContext, "unhandled_exception", F); + Exceptions->pushHandler(UnhandledBB); + // User types first: prototypes may name them in parameters/returns. runTypePrePass(Statements); @@ -536,6 +544,15 @@ void CodeGen::compile(const std::vector<std::unique_ptr<StmtAST>> &Statements) { Builder->CreateRet(ConstantInt::get(*TheContext, APInt(32, 0))); } + auto *Exception = Exceptions->popHandler(); + Builder->SetInsertPoint(UnhandledBB); + Exceptions->beginCatch(Exception); + Exceptions->endCatch(); + Value *Msg = Builder->CreateGlobalStringPtr("Unhandled exception. Program terminated.\n", + "err_unhandled", 0, TheModule.get()); + Builder->CreateCall(PrintfFunc, {Msg}); + Builder->CreateRet(ConstantInt::get(Type::getInt32Ty(*TheContext), 1)); + if (verifyModule(*TheModule, &errs())) { errs() << "fatal error: LLVM module verification failed\n"; HadError = true; @@ -849,6 +866,27 @@ bool CodeGen::marshalCallArgs(const std::string &Callee, return true; } +void CodeGen::emitTryStmt(TryStmtAST *Stmt) { + auto *F = Builder->GetInsertBlock()->getParent(); + auto *CatchBB = BasicBlock::Create(*TheContext, "catch", F); + auto *AfterBB = BasicBlock::Create(*TheContext, "try_cont", F); + auto SavedArrays = Arrays->snapshotTable(); + + Exceptions->pushHandler(CatchBB); + for (const auto &S : Stmt->getBody()) emitStmt(S.get()); + auto *Exception = Exceptions->popHandler(); + if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(AfterBB); + Arrays->exchangeTable(SavedArrays); + + Builder->SetInsertPoint(CatchBB); + Exceptions->beginCatch(Exception); + for (const auto &S : Stmt->getCatchBody()) emitStmt(S.get()); + Exceptions->endCatch(); + if (!Builder->GetInsertBlock()->getTerminator()) Builder->CreateBr(AfterBB); + Arrays->exchangeTable(std::move(SavedArrays)); + Builder->SetInsertPoint(AfterBB); +} + void CodeGen::emitIfStmt(IfStmtAST *Stmt) { Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; @@ -1000,16 +1038,6 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { void CodeGen::emitStmt(StmtAST *Stmt) { if (!Stmt) return; - if (auto *ArrDecl = dynamic_cast<ArrayDeclareStmtAST*>(Stmt)) { - Arrays->emitArrayDeclare(ArrDecl, *this); - return; - } - - if (auto *ArrAssign = dynamic_cast<ArrayAssignStmtAST*>(Stmt)) { - Arrays->emitArrayAssign(ArrAssign, *this); - return; - } - if (auto *FuncDef = dynamic_cast<FunctionDefAST*>(Stmt)) { BasicBlock *SavedBlock = Builder->GetInsertBlock(); // Function bodies get a fresh array scope (metadata SSA values belong @@ -1025,6 +1053,30 @@ void CodeGen::emitStmt(StmtAST *Stmt) { return; } + // Keep diagnosing statements after RETURN/THROW without appending code + // to a terminated block or making those statements reachable. + if (Builder->GetInsertBlock()->getTerminator()) { + auto *F = Builder->GetInsertBlock()->getParent(); + Builder->SetInsertPoint(BasicBlock::Create(*TheContext, "unreachable_code", F)); + } + + if (auto *Try = dynamic_cast<TryStmtAST*>(Stmt)) { + emitTryStmt(Try); + return; + } + if (dynamic_cast<ThrowStmtAST*>(Stmt)) { + Exceptions->emitThrow(); + return; + } + if (auto *ArrDecl = dynamic_cast<ArrayDeclareStmtAST*>(Stmt)) { + Arrays->emitArrayDeclare(ArrDecl, *this); + return; + } + if (auto *ArrAssign = dynamic_cast<ArrayAssignStmtAST*>(Stmt)) { + Arrays->emitArrayAssign(ArrAssign, *this); + return; + } + if (auto *Call = dynamic_cast<CallStmtAST*>(Stmt)) { std::vector<Value*> Args; if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Call->getLine(), Args)) return; 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(); +} diff --git a/lib/CodeGen/FileHandler.cc b/lib/CodeGen/FileHandler.cc index 275139a..66a7606 100644 --- a/lib/CodeGen/FileHandler.cc +++ b/lib/CodeGen/FileHandler.cc @@ -1,5 +1,6 @@ #include "cps/FileHandler.h" #include "cps/RuntimeCheck.h" +#include "cps/ExceptionHandler.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Function.h" @@ -9,8 +10,8 @@ using namespace llvm; using namespace cps; FileHandler::FileHandler(LLVMContext &Ctx, IRBuilder<> &B, llvm::Module &M, - RuntimeCheck &RC) - : Context(Ctx), Builder(B), Module(M), Checker(RC) { + RuntimeCheck &RC, ExceptionHandler &EH) + : Context(Ctx), Builder(B), Module(M), Checker(RC), Exceptions(EH) { // Nothing is declared or emitted here: the whole runtime is synthesized // lazily by ensureRuntime() so file-free programs keep their IR unchanged. } @@ -57,21 +58,6 @@ void FileHandler::setupGlobals() { ModeReadStr = Builder.CreateGlobalStringPtr("r", "file_mode_r", 0, &Module); ModeWriteStr = Builder.CreateGlobalStringPtr("w", "file_mode_w", 0, &Module); ModeAppendStr = Builder.CreateGlobalStringPtr("a", "file_mode_a", 0, &Module); - - ErrOpenFail = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: Cannot open file '%s'\n", "err_file_open", 0, &Module); - ErrAlreadyOpen = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: File '%s' is already open\n", "err_file_already_open", 0, &Module); - ErrTableFull = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: Too many open files\n", "err_file_table_full", 0, &Module); - ErrNotOpen = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: File '%s' is not open\n", "err_file_not_open", 0, &Module); - ErrNotRead = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: File '%s' is not open for READ\n", "err_file_not_read", 0, &Module); - ErrNotWrite = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: File '%s' is not open for WRITE\n", "err_file_not_write", 0, &Module); - ErrPastEof = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: Read past end of file '%s'\n", "err_file_past_eof", 0, &Module); } void FileHandler::ensureRuntime() { @@ -97,6 +83,7 @@ void FileHandler::ensureRuntime() { Function *FileHandler::createHelper(const char *Name, FunctionType *FT) { Function *F = Function::Create(FT, Function::InternalLinkage, Name, &Module); + Exceptions.prepareFunction(F); BasicBlock *Entry = BasicBlock::Create(Context, "entry", F); Builder.SetInsertPoint(Entry); return F; @@ -108,11 +95,11 @@ Value *FileHandler::emitSlotGEP(GlobalVariable *Table, Type *ElemTy, Value *Idx) } // Shared prologue of every helper that operates on an already-open file: -// look the name up, die with "not open" on a miss, return the slot index. +// look the name up, throw on a miss, return the slot index. Value *FileHandler::emitLookupOrFail(Value *Name, Value *Line) { Value *Idx = Builder.CreateCall(FindFn, {Name}, "slot"); Value *NotOpen = Builder.CreateICmpSLT(Idx, ConstantInt::get(Context, APInt(64, 0)), "not_open"); - Checker.emitErrorAndExit(NotOpen, ErrNotOpen, {Line, Name}); + Checker.emitCheck(NotOpen); return Idx; } @@ -184,7 +171,7 @@ void FileHandler::buildOpenFn() { Value *Existing = Builder.CreateCall(FindFn, {Name}, "existing"); Value *AlreadyOpen = Builder.CreateICmpSGE(Existing, ConstantInt::get(Context, APInt(64, 0)), "already_open"); - Checker.emitErrorAndExit(AlreadyOpen, ErrAlreadyOpen, {Line, Name}); + Checker.emitCheck(AlreadyOpen); // First-free-slot scan; SlotVar stays -1 when the table is full. AllocaInst *IdxVar = Builder.CreateAlloca(I64, nullptr, "idx"); @@ -221,7 +208,7 @@ void FileHandler::buildOpenFn() { Builder.SetInsertPoint(AfterBB); Value *Slot = Builder.CreateLoad(I64, SlotVar, "free_slot"); Value *NoFree = Builder.CreateICmpSLT(Slot, ConstantInt::get(Context, APInt(64, 0)), "table_full"); - Checker.emitErrorAndExit(NoFree, ErrTableFull, {Line}); + Checker.emitCheck(NoFree); // Branch-free fopen-mode selection: 1 -> "r", 2 -> "w", 3 -> "a". Value *IsWrite = Builder.CreateICmpEQ(Mode, ConstantInt::get(I8, uint8_t(FileMode::Write))); @@ -231,7 +218,7 @@ void FileHandler::buildOpenFn() { Value *Handle = Builder.CreateCall(FopenFunc, {Name, ModeStr}, "handle"); Value *OpenFailed = Builder.CreateICmpEQ(Handle, ConstantPointerNull::get(PtrTy), "open_failed"); - Checker.emitErrorAndExit(OpenFailed, ErrOpenFail, {Line, Name}); + Checker.emitCheck(OpenFailed); Builder.CreateStore(Name, emitSlotGEP(FileNames, PtrTy, Slot)); Builder.CreateStore(Handle, emitSlotGEP(FileHandles, PtrTy, Slot)); @@ -241,7 +228,7 @@ void FileHandler::buildOpenFn() { // ptr __cps_file_read(ptr name, i32 line): read one line into a fresh heap // STRING ('\n' consumed and stripped, trailing '\r' dropped). Reading at EOF -// is a fatal error — CIE programs are expected to guard with EOF(). +// throws an exception — CIE programs are expected to guard with EOF(). void FileHandler::buildReadFn() { PointerType *PtrTy = PointerType::getUnqual(Context); Type *I64 = Type::getInt64Ty(Context); @@ -257,13 +244,13 @@ void FileHandler::buildReadFn() { Value *Idx = emitLookupOrFail(Name, Line); Value *Mode = Builder.CreateLoad(I8, emitSlotGEP(FileModes, I8, Idx), "mode"); Value *NotRead = Builder.CreateICmpNE(Mode, ConstantInt::get(I8, uint8_t(FileMode::Read)), "not_read"); - Checker.emitErrorAndExit(NotRead, ErrNotRead, {Line, Name}); + Checker.emitCheck(NotRead); Value *Handle = Builder.CreateLoad(PtrTy, emitSlotGEP(FileHandles, PtrTy, Idx), "handle"); Value *MinusOne = ConstantInt::get(Context, APInt(32, -1, /*isSigned=*/true)); Value *Peek = Builder.CreateCall(FgetcFunc, {Handle}, "peek"); Value *AtEof = Builder.CreateICmpEQ(Peek, MinusOne, "at_eof"); - Checker.emitErrorAndExit(AtEof, ErrPastEof, {Line, Name}); + Checker.emitCheck(AtEof); Builder.CreateCall(UngetcFunc, {Peek, Handle}); // Growing buffer: cap doubles, always leaving room for byte + NUL. @@ -361,7 +348,7 @@ void FileHandler::buildWriteFn() { Value *Idx = emitLookupOrFail(Name, Line); Value *Mode = Builder.CreateLoad(I8, emitSlotGEP(FileModes, I8, Idx), "mode"); Value *IsReadMode = Builder.CreateICmpEQ(Mode, ConstantInt::get(I8, uint8_t(FileMode::Read)), "read_only"); - Checker.emitErrorAndExit(IsReadMode, ErrNotWrite, {Line, Name}); + Checker.emitCheck(IsReadMode); Value *Handle = Builder.CreateLoad(PtrTy, emitSlotGEP(FileHandles, PtrTy, Idx), "handle"); Builder.CreateCall(FputsFunc, {Data, Handle}); @@ -407,7 +394,7 @@ void FileHandler::buildEofFn() { Value *Idx = emitLookupOrFail(Name, Line); Value *Mode = Builder.CreateLoad(I8, emitSlotGEP(FileModes, I8, Idx), "mode"); Value *NotRead = Builder.CreateICmpNE(Mode, ConstantInt::get(I8, uint8_t(FileMode::Read)), "not_read"); - Checker.emitErrorAndExit(NotRead, ErrNotRead, {Line, Name}); + Checker.emitCheck(NotRead); Value *Handle = Builder.CreateLoad(PtrTy, emitSlotGEP(FileHandles, PtrTy, Idx), "handle"); Value *Ch = Builder.CreateCall(FgetcFunc, {Handle}, "c"); @@ -420,7 +407,7 @@ void FileHandler::buildEofFn() { void FileHandler::emitOpen(Value *NameStr, FileMode Mode, int Line) { if (!NameStr) return; ensureRuntime(); - Builder.CreateCall(OpenFn, {NameStr, + Exceptions.emitCall(OpenFn, {NameStr, ConstantInt::get(Type::getInt8Ty(Context), uint8_t(Mode)), ConstantInt::get(Context, APInt(32, Line))}); } @@ -428,25 +415,25 @@ void FileHandler::emitOpen(Value *NameStr, FileMode Mode, int Line) { Value *FileHandler::emitRead(Value *NameStr, int Line) { if (!NameStr) return nullptr; ensureRuntime(); - return Builder.CreateCall(ReadFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}, + return Exceptions.emitCall(ReadFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}, "file_line"); } void FileHandler::emitWrite(Value *NameStr, Value *DataStr, int Line) { if (!NameStr || !DataStr) return; ensureRuntime(); - Builder.CreateCall(WriteFn, {NameStr, DataStr, ConstantInt::get(Context, APInt(32, Line))}); + Exceptions.emitCall(WriteFn, {NameStr, DataStr, ConstantInt::get(Context, APInt(32, Line))}); } void FileHandler::emitClose(Value *NameStr, int Line) { if (!NameStr) return; ensureRuntime(); - Builder.CreateCall(CloseFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}); + Exceptions.emitCall(CloseFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}); } Value *FileHandler::emitEof(Value *NameStr, int Line) { if (!NameStr) return nullptr; ensureRuntime(); - return Builder.CreateCall(EofFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}, + return Exceptions.emitCall(EofFn, {NameStr, ConstantInt::get(Context, APInt(32, Line))}, "file_eof"); } diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 2589797..afa6074 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -1,5 +1,6 @@ #include "cps/FunctionGen.h" #include "cps/CodeGen.h" +#include "cps/ExceptionHandler.h" #include <cstdio> using namespace llvm; @@ -13,7 +14,9 @@ static bool isReservedRuntimeName(const std::string &Name) { "strcpy", "strcat", "toupper", "tolower", "sprintf", "strtol", "strtod", "memset", "exit", "fopen", "fclose", "fgetc", "ungetc", "fputs", "fputc", - "realloc", "strcmp", + "realloc", "strcmp", "__cxa_allocate_exception", "__cxa_throw", + "__cxa_begin_catch", "__cxa_end_catch", "__gxx_personality_v0", + "__gxx_personality_seh0", "__gxx_personality_sj0", "_Unwind_Resume", "_ZTIi", }; for (const char *R : Reserved) { if (Name == R) return true; @@ -149,6 +152,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { if (!F) { F = Function::Create(FT, Function::ExternalLinkage, Proto->getName(), &Module); } + Exceptions.prepareFunction(F); // Keep the first registration: on a duplicate definition the first one // wins and the second is rejected by emitFunctionDef. @@ -204,7 +208,7 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, } static Value *GenerateCall(llvm::Module &Module, - llvm::IRBuilder<> &Builder, + ExceptionHandler &Exceptions, const std::string &CalleeName, const std::vector<llvm::Value*> &Args, bool &HadError) { @@ -216,21 +220,22 @@ static Value *GenerateCall(llvm::Module &Module, } if (CalleeF->getReturnType()->isVoidTy()) { - return Builder.CreateCall(CalleeF, Args); + return Exceptions.emitCall(CalleeF, Args); } - return Builder.CreateCall(CalleeF, Args, "calltmp"); + return Exceptions.emitCall(CalleeF, Args, "calltmp"); } llvm::Value *FunctionGen::emitCallExpr(CallExprAST *Call, const std::vector<llvm::Value*> &Args) { - return GenerateCall(Module, Builder, Call->getCallee(), Args, HadError); + return GenerateCall(Module, Exceptions, Call->getCallee(), Args, HadError); } void FunctionGen::emitCallStmt(CallStmtAST *Call, const std::vector<llvm::Value*> &Args) { - GenerateCall(Module, Builder, Call->getCallee(), Args, HadError); + GenerateCall(Module, Exceptions, Call->getCallee(), Args, HadError); } void FunctionGen::emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal) { + Exceptions.emitReturnCleanup(); if (RetVal) { Builder.CreateRet(RetVal); } else { diff --git a/lib/CodeGen/RuntimeCheck.cc b/lib/CodeGen/RuntimeCheck.cc index 8075873..ee270e5 100644 --- a/lib/CodeGen/RuntimeCheck.cc +++ b/lib/CodeGen/RuntimeCheck.cc @@ -1,4 +1,5 @@ #include "cps/RuntimeCheck.h" +#include "cps/ExceptionHandler.h" #include "cps/TypeSystem.h" // kEnumOrdinalBase #include "llvm/IR/Function.h" #include "llvm/IR/Type.h" @@ -7,52 +8,16 @@ using namespace llvm; using namespace cps; -RuntimeCheck::RuntimeCheck(Module &M, LLVMContext &C, IRBuilder<> &B) - : TheModule(M), TheContext(C), Builder(B) { - setupExternalFunctions(); -} - -void RuntimeCheck::setupExternalFunctions() { - std::vector<Type*> PrintfArgs; - PrintfArgs.push_back(PointerType::getUnqual(TheContext)); - FunctionType *PrintfType = FunctionType::get(Type::getInt32Ty(TheContext), PrintfArgs, true); - PrintfFunc = TheModule.getOrInsertFunction("printf", PrintfType); - - std::vector<Type*> ExitArgs; - ExitArgs.push_back(Type::getInt32Ty(TheContext)); - FunctionType *ExitType = FunctionType::get(Type::getVoidTy(TheContext), ExitArgs, false); - ExitFunc = TheModule.getOrInsertFunction("exit", ExitType); - - DivZeroMsg = Builder.CreateGlobalStringPtr("[Fatal] line %d: Division by zero\n", "err_div_zero", 0, &TheModule); - OutOfBoundsMsg = Builder.CreateGlobalStringPtr("[Fatal] line %d: Array index out of bounds\n", "err_bounds", 0, &TheModule); - EnumRangeMsg = Builder.CreateGlobalStringPtr("[Fatal] line %d: Value out of range for enum %s\n", "err_enum_range", 0, &TheModule); - NullDerefMsg = Builder.CreateGlobalStringPtr("[Fatal] line %d: Dereference of an unset pointer\n", "err_null_deref", 0, &TheModule); - ArrayArgBoundsMsg = Builder.CreateGlobalStringPtr( - "[Fatal] line %d: Array argument bounds [%lld:%lld] do not match the declared parameter bounds [%lld:%lld]\n", - "err_arr_arg_bounds", 0, &TheModule); -} - -void RuntimeCheck::emitErrorAndExit(Value *Condition, Value *Msg, int Line) { - emitErrorAndExit(Condition, Msg, {ConstantInt::get(TheContext, APInt(32, Line))}); -} +RuntimeCheck::RuntimeCheck(LLVMContext &C, IRBuilder<> &B, ExceptionHandler &EH) + : TheContext(C), Builder(B), Exceptions(EH) {} -void RuntimeCheck::emitErrorAndExit(Value *Condition, Value *Msg, ArrayRef<Value *> FmtArgs) { +void RuntimeCheck::emitCheck(Value *Condition) { Function *TheFunction = Builder.GetInsertBlock()->getParent(); - BasicBlock *FailBB = BasicBlock::Create(TheContext, "check_fail", TheFunction); BasicBlock *ContBB = BasicBlock::Create(TheContext, "check_cont", TheFunction); - Builder.CreateCondBr(Condition, FailBB, ContBB); - Builder.SetInsertPoint(FailBB); - std::vector<Value*> PrintArgs; - PrintArgs.push_back(Msg); - PrintArgs.insert(PrintArgs.end(), FmtArgs.begin(), FmtArgs.end()); - Builder.CreateCall(PrintfFunc, PrintArgs); - - Builder.CreateCall(ExitFunc, ConstantInt::get(TheContext, APInt(32, 1))); - Builder.CreateUnreachable(); - + Exceptions.emitThrow(); Builder.SetInsertPoint(ContBB); } @@ -63,7 +28,7 @@ void RuntimeCheck::emitDivZeroCheck(Value *Divisor, int Line) { } else { IsZero = Builder.CreateICmpEQ(Divisor, ConstantInt::get(Divisor->getType(), 0), "is_zero"); } - emitErrorAndExit(IsZero, DivZeroMsg, Line); + emitCheck(IsZero); } void RuntimeCheck::emitIndexCheck(Value *Index, Value *Lower, Value *Upper, int Line) { @@ -71,7 +36,7 @@ void RuntimeCheck::emitIndexCheck(Value *Index, Value *Lower, Value *Upper, int Value *TooHigh = Builder.CreateICmpSGT(Index, Upper, "too_high"); Value *OutOfBounds = Builder.CreateOr(TooLow, TooHigh, "out_of_bounds"); - emitErrorAndExit(OutOfBounds, OutOfBoundsMsg, Line); + emitCheck(OutOfBounds); } void RuntimeCheck::emitEnumRangeCheck(Value *Ordinal, uint64_t ValueCount, @@ -83,16 +48,14 @@ void RuntimeCheck::emitEnumRangeCheck(Value *Ordinal, uint64_t ValueCount, Value *TooHigh = Builder.CreateICmpSGE(Ordinal, Hi, "enum_too_high"); Value *OutOfRange = Builder.CreateOr(TooLow, TooHigh, "enum_out_of_range"); - Value *NameStr = Builder.CreateGlobalStringPtr(TypeName, "enum_name", 0, &TheModule); - emitErrorAndExit(OutOfRange, EnumRangeMsg, - {ConstantInt::get(TheContext, APInt(32, Line)), NameStr}); + emitCheck(OutOfRange); } void RuntimeCheck::emitNullDerefCheck(Value *Ptr, int Line) { Value *IsNull = Builder.CreateICmpEQ(Ptr, ConstantPointerNull::get(PointerType::getUnqual(TheContext)), "ptr_is_null"); - emitErrorAndExit(IsNull, NullDerefMsg, Line); + emitCheck(IsNull); } void RuntimeCheck::emitArrayArgBoundsCheck(Value *ActualLower, Value *ActualUpper, @@ -104,7 +67,5 @@ void RuntimeCheck::emitArrayArgBoundsCheck(Value *ActualLower, Value *ActualUppe Value *HiDiff = Builder.CreateICmpNE(ActualUpper, DeclHi, "arg_hi_ne"); Value *Mismatch = Builder.CreateOr(LoDiff, HiDiff, "arg_bounds_ne"); - emitErrorAndExit(Mismatch, ArrayArgBoundsMsg, - {ConstantInt::get(TheContext, APInt(32, Line)), - ActualLower, ActualUpper, DeclLo, DeclHi}); + emitCheck(Mismatch); } |
