aboutsummaryrefslogtreecommitdiff
path: root/lib/CodeGen
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-15 02:41:11 +0000
committer_ov4 <ov4@ov4.org>2026-10-15 02:41:11 +0000
commitc4565386553ea86308a9487650d2435804893ed0 (patch)
treeeae3556d3a1e05b3f91b13b218cab3a72eb3bbef /lib/CodeGen
parentab86ae0284469a9a941f5b196f9cc99a90305e3a (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'lib/CodeGen')
-rw-r--r--lib/CodeGen/CodeGen.cc2
-rw-r--r--lib/CodeGen/FileHandler.cc33
-rw-r--r--lib/CodeGen/RuntimeCheck.cc50
3 files changed, 67 insertions, 18 deletions
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<TypeSystem>(*TheContext);
Exceptions = std::make_unique<ExceptionHandler>(*TheContext, *Builder, *TheModule);
- RuntimeChecker = std::make_unique<RuntimeCheck>(*TheContext, *Builder, *Exceptions);
+ RuntimeChecker = std::make_unique<RuntimeCheck>(*TheModule, *TheContext, *Builder, *Exceptions);
Arrays = std::make_unique<ArrayHandler>(*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<Type*> 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<Value *> 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<Value*> 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});
}