From c4565386553ea86308a9487650d2435804893ed0 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 15 Oct 2026 02:41:11 +0000 Subject: fix(codegen): restore runtime diagnostics before throwing Reuse historical runtime messages and source-line reporting for arithmetic, array, enum, pointer and file checks while preserving anonymous exceptions. Update the manuals and subsystem guidance to describe diagnostic output. Co-Authored-By: Claude Fable 5.1 --- AGENTS/ARCH.md | 3 +- AGENTS/CONVENTIONS.md | 7 +++-- AGENTS/EXTENDING.md | 9 +++--- AGENTS/PITFALLS.md | 8 +++-- AGENTS/SUBSYSTEMS.md | 5 +-- doc/cpsc.1.md | 2 +- doc/language/cpsc-language-overview.7.md | 2 +- doc/language/keyword/cpsc-keyword-try.7.md | 2 +- include/cps/FileHandler.h | 4 +++ include/cps/RuntimeCheck.h | 24 +++++++++++--- lib/CodeGen/CodeGen.cc | 2 +- lib/CodeGen/FileHandler.cc | 33 ++++++++++++++------ lib/CodeGen/RuntimeCheck.cc | 50 +++++++++++++++++++++++++----- 13 files changed, 113 insertions(+), 38 deletions(-) diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md index f48fbb1..c4cb8d6 100644 --- a/AGENTS/ARCH.md +++ b/AGENTS/ARCH.md @@ -72,7 +72,8 @@ release because MSYS2 has no Pandoc package. Only the Linux build is verified. Generated IR uses the C++ exception ABI; native binaries must link its runtime (use LLVM 23's `clang++`, or `clang++-23` on Debian/Ubuntu). TRY/CATCH/ENDTRY and bare THROW are compiler extensions. -Runtime checks throw anonymous exceptions; the final handler in generated main +Runtime checks print the source line and error reason to stdout before throwing +anonymous exceptions; the final handler in generated main prints `Unhandled exception. Program terminated.` to stdout and returns 1. Exception execution is verified on Linux with LLVM 23; Windows/macOS are unverified. diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index 01d4ab2..59daa92 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -158,6 +158,7 @@ Its handler stack and catch depth are keyed by LLVM function, so synthesizing fi helpers never inherits the caller's catches. User calls, file runtime calls and THROW must use `ExceptionHandler::emitCall`; ordinary nonthrowing libc calls stay plain calls. RETURN must finish all active catches after evaluating its value. -`RuntimeCheck::emitCheck` throws without printing. Generated main catches anything -unhandled, prints the single generic message to stdout, and returns 1. Arrays in -TRY and CATCH use separate block scopes; scalars retain existing function scope. +`RuntimeCheck::emitCheck` prints the source line and error reason to stdout before +throwing. Generated main catches anything unhandled, prints the generic termination +message to stdout, and returns 1. Explicit bare THROW prints no diagnostic itself. +Arrays in TRY and CATCH use separate block scopes; scalars retain existing function scope. diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md index 2b1549f..a396867 100644 --- a/AGENTS/EXTENDING.md +++ b/AGENTS/EXTENDING.md @@ -60,10 +60,11 @@ degrade rule in `ParseIdentifierExpr`/`ParseAssignStmt` if the new form can collapse to a legacy node. **New runtime check:** add `emitXxxCheck` in `RuntimeCheck` (build an i1 -fail-condition and call `emitCheck` — it splits the block and leaves the builder at -the continuation), then call it from the emit site. Failure throws an anonymous -exception without printing; generated main supplies the uncaught message and -status 1. Precedents: `emitIndexCheck`, `emitEnumRangeCheck`, `emitNullDerefCheck`, +fail-condition and call `emitCheck` with a diagnostic format and arguments — it +splits the block and leaves the builder at the continuation), then call it from +the emit site. Failure prints the source line and error reason to stdout before +throwing an anonymous exception; generated main supplies the uncaught message +and status 1. Precedents: `emitIndexCheck`, `emitEnumRangeCheck`, `emitNullDerefCheck`, `emitArrayArgBoundsCheck` and FileHandler's direct `emitCheck` calls. **New potentially throwing call:** route it through `ExceptionHandler::emitCall` diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index 0fe61fe..c740d2f 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -81,9 +81,11 @@ test programs. - **Exception extensions.** TRY requires exactly one parameterless CATCH and ENDTRY. Bare THROW raises a new anonymous exception even inside CATCH; there is no rethrow syntax, error object or what(). Runtime checks are catchable; - compiler errors are not. Uncaught exceptions print only the generic message - and return 1. Native binaries must link the C++ runtime with LLVM 23's - `clang++` (`clang++-23` on Debian/Ubuntu). + compiler errors are not. Runtime checks print the source line and error reason + to stdout before throwing, even when caught. Uncaught exceptions also print the + generic termination message and return 1. Bare THROW prints no diagnostic itself. + Native binaries must link the C++ runtime with LLVM 23's `clang++` + (`clang++-23` on Debian/Ubuntu). Unwinding does not free heap strings/arrays or close files. Linux LLVM 23 is verified; Windows and macOS exception execution is not. diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index 7d1ea5c..54e15ef 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -65,8 +65,9 @@ context or language-level inheritance yet. dereferences and array-parameter bounds contracts are checked at their emit sites. `ArithmeticHandler` calls `emitDivZeroCheck` before `/`, DIV and MOD; the check accepts integer and floating-point divisors, including both floating-point signed zeros. -`emitCheck(Cond)` splits the block and calls `ExceptionHandler::emitThrow` on failure. -No check prints an error; source-line parameters remain reserved for future diagnostics. +`emitCheck(Cond, Msg, Args)` splits the block, prints the source line and error +reason to stdout, and calls `ExceptionHandler::emitThrow` on failure. File helper +checks receive the source line as a runtime argument. **Exceptions** (`ExceptionHandler`): holds Context/Builder/Module refs and a map of per-function handler stacks and active catch depths. TRY pushes a catch target and diff --git a/doc/cpsc.1.md b/doc/cpsc.1.md index 063e2c1..c58f1ad 100644 --- a/doc/cpsc.1.md +++ b/doc/cpsc.1.md @@ -84,7 +84,7 @@ clang++ test.ll -o test With **-r**, return the executed program's exit status. In PATH mode, failure to launch `lli` or a child-process crash returns 1. -An uncaught program exception returns 1 after printing `Unhandled exception. Program terminated.` to standard output. Runtime check failures are exceptions and can be handled with TRY/CATCH/ENDTRY. +An uncaught program exception returns 1 after printing `Unhandled exception. Program terminated.` to standard output. Runtime check failures print the source line and error reason to standard output before throwing and can be handled with TRY/CATCH/ENDTRY. # NOTES diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index e729a6b..530eb45 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -18,7 +18,7 @@ Literals include integers, decimal real numbers, TRUE/FALSE, strings in double q Declare variables before use. See DECLARE for built-in types and arrays, TYPE for records/enumerations/pointers, and CLASS for public/private fields. -The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions with stack unwinding. Runtime check failures can be caught; an uncaught exception prints a generic message and returns status 1. There is no error object or what() support. +The compiler extensions TRY/CATCH/ENDTRY and THROW support anonymous exceptions with stack unwinding. Runtime check failures print the source line and error reason to standard output before throwing and can be caught. An uncaught exception also prints a generic termination message and returns status 1. There is no error object or what() support. # KEYWORDS diff --git a/doc/language/keyword/cpsc-keyword-try.7.md b/doc/language/keyword/cpsc-keyword-try.7.md index 5892027..bbea138 100644 --- a/doc/language/keyword/cpsc-keyword-try.7.md +++ b/doc/language/keyword/cpsc-keyword-try.7.md @@ -24,7 +24,7 @@ Normal execution skips CATCH. An exception stops the current operation, unwinds THROW takes no argument and always raises a new anonymous exception. An exception raised inside CATCH propagates to an enclosing TRY or a caller's handler; the same CATCH cannot catch it again. RETURN may leave either body, and leaving a handler finishes its catch state. -Runtime checks throw the same anonymous exceptions for division by zero, array index errors, null pointer dereferences, invalid enumeration values, array-parameter bounds mismatches, and file misuse. Compiler errors cannot be caught. A caught exception prints nothing automatically. If no handler catches an exception, the program prints `Unhandled exception. Program terminated.` to standard output and returns status 1. +Runtime checks throw the same anonymous exceptions for division by zero, array index errors, null pointer dereferences, invalid enumeration values, array-parameter bounds mismatches, and file misuse. Before throwing, a runtime check prints the source line and error reason to standard output, even when CATCH handles the exception. Explicit THROW prints no diagnostic itself. Compiler errors cannot be caught. If no handler catches an exception, the program prints `Unhandled exception. Program terminated.` to standard output and returns status 1. Arrays declared inside either body remain local to that block. Stack unwinding does not automatically free heap strings or arrays, close files, or run user-defined destructors. Exception execution is verified on Linux with LLVM 23; Windows and macOS remain unverified. diff --git a/include/cps/FileHandler.h b/include/cps/FileHandler.h index 8b8ddf4..50593de 100644 --- a/include/cps/FileHandler.h +++ b/include/cps/FileHandler.h @@ -38,6 +38,10 @@ class FileHandler { llvm::Value *ModeReadStr = nullptr, *ModeWriteStr = nullptr, *ModeAppendStr = nullptr; + llvm::Value *ErrOpenFail = nullptr, *ErrAlreadyOpen = nullptr, *ErrTableFull = nullptr, + *ErrNotOpen = nullptr, *ErrNotRead = nullptr, *ErrNotWrite = nullptr, + *ErrPastEof = nullptr; + // synthesized helpers (internal linkage; pseudocode identifiers cannot // start with '_', so these names are un-collidable) llvm::Function *FindFn = nullptr, *OpenFn = nullptr, *ReadFn = nullptr, diff --git a/include/cps/RuntimeCheck.h b/include/cps/RuntimeCheck.h index d9a69b6..f5a8aa7 100644 --- a/include/cps/RuntimeCheck.h +++ b/include/cps/RuntimeCheck.h @@ -10,15 +10,24 @@ namespace cps { class ExceptionHandler; class RuntimeCheck { + llvm::Module &TheModule; llvm::LLVMContext &TheContext; llvm::IRBuilder<> &Builder; + + llvm::FunctionCallee PrintfFunc; ExceptionHandler &Exceptions; + llvm::Value *DivZeroMsg; + llvm::Value *OutOfBoundsMsg; + llvm::Value *EnumRangeMsg; + llvm::Value *NullDerefMsg; + llvm::Value *ArrayArgBoundsMsg; -public: - RuntimeCheck(llvm::LLVMContext &C, llvm::IRBuilder<> &B, ExceptionHandler &EH); + void setupExternalFunctions(); + void emitCheck(llvm::Value *Condition, llvm::Value *Msg, int Line); - // Split the block, throwing an anonymous exception when Condition is true. - void emitCheck(llvm::Value *Condition); +public: + RuntimeCheck(llvm::Module &M, llvm::LLVMContext &C, llvm::IRBuilder<> &B, + ExceptionHandler &EH); void emitDivZeroCheck(llvm::Value *Divisor, int Line); void emitIndexCheck(llvm::Value *Index, llvm::Value *Lower, llvm::Value *Upper, int Line); @@ -35,6 +44,13 @@ public: void emitArrayArgBoundsCheck(llvm::Value *ActualLower, llvm::Value *ActualUpper, int64_t DeclaredLower, int64_t DeclaredUpper, int Line); + // Print the diagnostic, then throw an anonymous exception on failure. + // Runtime-computed variant: FmtArgs are the printf varargs after Msg + // (e.g. {i32 line, ptr filename}). Needed when the failure site is inside + // a synthesized helper function, where the line number is an argument + // rather than a compile-time constant. + void emitCheck(llvm::Value *Condition, llvm::Value *Msg, + llvm::ArrayRef FmtArgs); }; } diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index b86a0ae..7e463db 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -71,7 +71,7 @@ CodeGen::CodeGen() { Types = std::make_unique(*TheContext); Exceptions = std::make_unique(*TheContext, *Builder, *TheModule); - RuntimeChecker = std::make_unique(*TheContext, *Builder, *Exceptions); + RuntimeChecker = std::make_unique(*TheModule, *TheContext, *Builder, *Exceptions); Arrays = std::make_unique(*TheContext, *Builder, diff --git a/lib/CodeGen/FileHandler.cc b/lib/CodeGen/FileHandler.cc index f2e7824..ba32771 100644 --- a/lib/CodeGen/FileHandler.cc +++ b/lib/CodeGen/FileHandler.cc @@ -58,6 +58,21 @@ void FileHandler::setupGlobals() { ModeReadStr = Builder.CreateGlobalString("r", "file_mode_r", 0, &Module); ModeWriteStr = Builder.CreateGlobalString("w", "file_mode_w", 0, &Module); ModeAppendStr = Builder.CreateGlobalString("a", "file_mode_a", 0, &Module); + + ErrOpenFail = Builder.CreateGlobalString( + "[Fatal] line %d: Cannot open file '%s'\n", "err_file_open", 0, &Module); + ErrAlreadyOpen = Builder.CreateGlobalString( + "[Fatal] line %d: File '%s' is already open\n", "err_file_already_open", 0, &Module); + ErrTableFull = Builder.CreateGlobalString( + "[Fatal] line %d: Too many open files\n", "err_file_table_full", 0, &Module); + ErrNotOpen = Builder.CreateGlobalString( + "[Fatal] line %d: File '%s' is not open\n", "err_file_not_open", 0, &Module); + ErrNotRead = Builder.CreateGlobalString( + "[Fatal] line %d: File '%s' is not open for READ\n", "err_file_not_read", 0, &Module); + ErrNotWrite = Builder.CreateGlobalString( + "[Fatal] line %d: File '%s' is not open for WRITE\n", "err_file_not_write", 0, &Module); + ErrPastEof = Builder.CreateGlobalString( + "[Fatal] line %d: Read past end of file '%s'\n", "err_file_past_eof", 0, &Module); } void FileHandler::ensureRuntime() { @@ -95,11 +110,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, throw on a miss, return the slot index. +// look the name up, report and 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.emitCheck(NotOpen); + Checker.emitCheck(NotOpen, ErrNotOpen, {Line, Name}); return Idx; } @@ -171,7 +186,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.emitCheck(AlreadyOpen); + Checker.emitCheck(AlreadyOpen, ErrAlreadyOpen, {Line, Name}); // First-free-slot scan; SlotVar stays -1 when the table is full. AllocaInst *IdxVar = Builder.CreateAlloca(I64, nullptr, "idx"); @@ -208,7 +223,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.emitCheck(NoFree); + Checker.emitCheck(NoFree, ErrTableFull, {Line}); // Branch-free fopen-mode selection: 1 -> "r", 2 -> "w", 3 -> "a". Value *IsWrite = Builder.CreateICmpEQ(Mode, ConstantInt::get(I8, uint8_t(FileMode::Write))); @@ -218,7 +233,7 @@ void FileHandler::buildOpenFn() { Value *Handle = Builder.CreateCall(FopenFunc, {Name, ModeStr}, "handle"); Value *OpenFailed = Builder.CreateICmpEQ(Handle, ConstantPointerNull::get(PtrTy), "open_failed"); - Checker.emitCheck(OpenFailed); + Checker.emitCheck(OpenFailed, ErrOpenFail, {Line, Name}); Builder.CreateStore(Name, emitSlotGEP(FileNames, PtrTy, Slot)); Builder.CreateStore(Handle, emitSlotGEP(FileHandles, PtrTy, Slot)); @@ -244,13 +259,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.emitCheck(NotRead); + Checker.emitCheck(NotRead, ErrNotRead, {Line, Name}); 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.emitCheck(AtEof); + Checker.emitCheck(AtEof, ErrPastEof, {Line, Name}); Builder.CreateCall(UngetcFunc, {Peek, Handle}); // Growing buffer: cap doubles, always leaving room for byte + NUL. @@ -348,7 +363,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.emitCheck(IsReadMode); + Checker.emitCheck(IsReadMode, ErrNotWrite, {Line, Name}); Value *Handle = Builder.CreateLoad(PtrTy, emitSlotGEP(FileHandles, PtrTy, Idx), "handle"); Builder.CreateCall(FputsFunc, {Data, Handle}); @@ -394,7 +409,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.emitCheck(NotRead); + Checker.emitCheck(NotRead, ErrNotRead, {Line, Name}); Value *Handle = Builder.CreateLoad(PtrTy, emitSlotGEP(FileHandles, PtrTy, Idx), "handle"); Value *Ch = Builder.CreateCall(FgetcFunc, {Handle}, "c"); diff --git a/lib/CodeGen/RuntimeCheck.cc b/lib/CodeGen/RuntimeCheck.cc index 2954af0..215702e 100644 --- a/lib/CodeGen/RuntimeCheck.cc +++ b/lib/CodeGen/RuntimeCheck.cc @@ -8,16 +8,46 @@ using namespace llvm; using namespace cps; -RuntimeCheck::RuntimeCheck(LLVMContext &C, IRBuilder<> &B, ExceptionHandler &EH) - : TheContext(C), Builder(B), Exceptions(EH) {} +RuntimeCheck::RuntimeCheck(Module &M, LLVMContext &C, IRBuilder<> &B, ExceptionHandler &EH) + : TheModule(M), TheContext(C), Builder(B), Exceptions(EH) { + setupExternalFunctions(); +} + +void RuntimeCheck::setupExternalFunctions() { + std::vector PrintfArgs; + PrintfArgs.push_back(PointerType::getUnqual(TheContext)); + FunctionType *PrintfType = FunctionType::get(Type::getInt32Ty(TheContext), PrintfArgs, true); + PrintfFunc = TheModule.getOrInsertFunction("printf", PrintfType); + + DivZeroMsg = Builder.CreateGlobalString("[Fatal] line %d: Division by zero\n", "err_div_zero", 0, &TheModule); + OutOfBoundsMsg = Builder.CreateGlobalString("[Fatal] line %d: Array index out of bounds\n", "err_bounds", 0, &TheModule); + EnumRangeMsg = Builder.CreateGlobalString("[Fatal] line %d: Value out of range for enum %s\n", "err_enum_range", 0, &TheModule); + NullDerefMsg = Builder.CreateGlobalString("[Fatal] line %d: Dereference of an unset pointer\n", "err_null_deref", 0, &TheModule); + ArrayArgBoundsMsg = Builder.CreateGlobalString( + "[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::emitCheck(Value *Condition, Value *Msg, int Line) { + emitCheck(Condition, Msg, {ConstantInt::get(TheContext, APInt(32, Line))}); +} -void RuntimeCheck::emitCheck(Value *Condition) { +void RuntimeCheck::emitCheck(Value *Condition, Value *Msg, ArrayRef FmtArgs) { 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 PrintArgs; + PrintArgs.push_back(Msg); + PrintArgs.insert(PrintArgs.end(), FmtArgs.begin(), FmtArgs.end()); + Builder.CreateCall(PrintfFunc, PrintArgs); + Exceptions.emitThrow(); + Builder.SetInsertPoint(ContBB); } @@ -28,7 +58,7 @@ void RuntimeCheck::emitDivZeroCheck(Value *Divisor, int Line) { } else { IsZero = Builder.CreateICmpEQ(Divisor, ConstantInt::get(Divisor->getType(), 0), "is_zero"); } - emitCheck(IsZero); + emitCheck(IsZero, DivZeroMsg, Line); } void RuntimeCheck::emitIndexCheck(Value *Index, Value *Lower, Value *Upper, int Line) { @@ -36,7 +66,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"); - emitCheck(OutOfBounds); + emitCheck(OutOfBounds, OutOfBoundsMsg, Line); } void RuntimeCheck::emitEnumRangeCheck(Value *Ordinal, uint64_t ValueCount, @@ -48,14 +78,16 @@ 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"); - emitCheck(OutOfRange); + Value *NameStr = Builder.CreateGlobalString(TypeName, "enum_name", 0, &TheModule); + emitCheck(OutOfRange, EnumRangeMsg, + {ConstantInt::get(TheContext, APInt(32, Line)), NameStr}); } void RuntimeCheck::emitNullDerefCheck(Value *Ptr, int Line) { Value *IsNull = Builder.CreateICmpEQ(Ptr, ConstantPointerNull::get(PointerType::getUnqual(TheContext)), "ptr_is_null"); - emitCheck(IsNull); + emitCheck(IsNull, NullDerefMsg, Line); } void RuntimeCheck::emitArrayArgBoundsCheck(Value *ActualLower, Value *ActualUpper, @@ -67,5 +99,7 @@ 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"); - emitCheck(Mismatch); + emitCheck(Mismatch, ArrayArgBoundsMsg, + {ConstantInt::get(TheContext, APInt(32, Line)), + ActualLower, ActualUpper, DeclLo, DeclHi}); } -- cgit v1.3.1-4-g156e