aboutsummaryrefslogtreecommitdiff
path: root/include/cps
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 14:52:57 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 14:52:57 +0000
commita484f314b719aec5ed7bdc6f08403342b2125561 (patch)
tree531e331cf823517eae4c350d37bca6a500b2de9f /include/cps
parent4297086c86f4d11e8a14b76b50705b419d6b7f13 (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 'include/cps')
-rw-r--r--include/cps/AST.h12
-rw-r--r--include/cps/CodeGen.h3
-rw-r--r--include/cps/ExceptionHandler.h48
-rw-r--r--include/cps/FileHandler.h7
-rw-r--r--include/cps/FunctionGen.h6
-rw-r--r--include/cps/Lexer.h7
-rw-r--r--include/cps/Parser.h1
-rw-r--r--include/cps/RuntimeCheck.h28
8 files changed, 86 insertions, 26 deletions
diff --git a/include/cps/AST.h b/include/cps/AST.h
index d98613d..e3c6bc5 100644
--- a/include/cps/AST.h
+++ b/include/cps/AST.h
@@ -93,6 +93,18 @@ public:
virtual ~StmtAST() = default;
};
+class TryStmtAST : public StmtAST {
+ std::vector<std::unique_ptr<StmtAST>> Body, CatchBody;
+public:
+ TryStmtAST(std::vector<std::unique_ptr<StmtAST>> Body,
+ std::vector<std::unique_ptr<StmtAST>> CatchBody)
+ : Body(std::move(Body)), CatchBody(std::move(CatchBody)) {}
+ const std::vector<std::unique_ptr<StmtAST>> &getBody() const { return Body; }
+ const std::vector<std::unique_ptr<StmtAST>> &getCatchBody() const { return CatchBody; }
+};
+
+class ThrowStmtAST : public StmtAST {};
+
class DeclareStmtAST : public StmtAST {
std::string Name;
std::string Type;
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h
index 2c837fd..acd6cb2 100644
--- a/include/cps/CodeGen.h
+++ b/include/cps/CodeGen.h
@@ -19,6 +19,7 @@ namespace cps {
class ArrayHandler;
class FileHandler;
+class ExceptionHandler;
class CodeGen {
std::unique_ptr<llvm::LLVMContext> TheContext;
@@ -28,6 +29,7 @@ class CodeGen {
std::unique_ptr<TypeSystem> Types;
std::unique_ptr<ArrayHandler> Arrays;
+ std::unique_ptr<ExceptionHandler> Exceptions;
std::unique_ptr<RuntimeCheck> RuntimeChecker;
std::unique_ptr<FunctionGen> FuncGen;
@@ -87,6 +89,7 @@ class CodeGen {
void emitOutputValue(llvm::Value *Val, const TypeInfo *TypeInfo, bool AppendNewline = true);
void emitIfStmt(IfStmtAST *Stmt);
+ void emitTryStmt(TryStmtAST *Stmt);
void emitWhileStmt(WhileStmtAST *Stmt);
void emitRepeatStmt(RepeatStmtAST *Stmt);
void emitForStmt(ForStmtAST *Stmt);
diff --git a/include/cps/ExceptionHandler.h b/include/cps/ExceptionHandler.h
new file mode 100644
index 0000000..5c846ae
--- /dev/null
+++ b/include/cps/ExceptionHandler.h
@@ -0,0 +1,48 @@
+#pragma once
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Module.h"
+#include <map>
+#include <vector>
+
+namespace cps {
+
+// C++ ABI exception emission. State is keyed by function because file helper
+// bodies and user functions are emitted using the same builder as their caller.
+class ExceptionHandler {
+ struct Handler {
+ llvm::BasicBlock *Target;
+ llvm::AllocaInst *Exception;
+ unsigned CatchDepth;
+ };
+ struct FunctionState {
+ std::vector<Handler> Handlers;
+ unsigned CatchDepth = 0;
+ };
+
+ llvm::LLVMContext &Context;
+ llvm::IRBuilder<> &Builder;
+ llvm::Module &Module;
+ std::map<llvm::Function *, FunctionState> States;
+ llvm::FunctionCallee Allocate, Throw, BeginCatch, EndCatch, Personality;
+ llvm::GlobalVariable *IntTypeInfo = nullptr;
+
+ void ensureRuntime();
+ llvm::BasicBlock *emitUnwindDestination();
+
+public:
+ ExceptionHandler(llvm::LLVMContext &C, llvm::IRBuilder<> &B, llvm::Module &M)
+ : Context(C), Builder(B), Module(M) {}
+
+ void prepareFunction(llvm::Function *F);
+ void pushHandler(llvm::BasicBlock *Target);
+ llvm::AllocaInst *popHandler();
+ void beginCatch(llvm::AllocaInst *Exception);
+ void endCatch();
+ void emitReturnCleanup();
+
+ llvm::Value *emitCall(llvm::FunctionCallee Callee, llvm::ArrayRef<llvm::Value *> Args,
+ const llvm::Twine &Name = "");
+ void emitThrow();
+};
+
+} // namespace cps
diff --git a/include/cps/FileHandler.h b/include/cps/FileHandler.h
index 3898a57..8b8ddf4 100644
--- a/include/cps/FileHandler.h
+++ b/include/cps/FileHandler.h
@@ -7,6 +7,7 @@
namespace cps {
class RuntimeCheck;
+class ExceptionHandler;
// Emits the text-file runtime: a fixed-size open-file table plus internal
// __cps_file_* helper functions, synthesized once on first use so programs
@@ -20,6 +21,7 @@ class FileHandler {
llvm::IRBuilder<> &Builder;
llvm::Module &Module;
RuntimeCheck &Checker;
+ ExceptionHandler &Exceptions;
static constexpr uint64_t TableSize = 16;
bool RuntimeEmitted = false;
@@ -35,9 +37,6 @@ class FileHandler {
llvm::GlobalVariable *FileModes = nullptr; // [TableSize x i8] 0 = free slot, else FileMode
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)
@@ -60,7 +59,7 @@ class FileHandler {
public:
FileHandler(llvm::LLVMContext &Ctx, llvm::IRBuilder<> &B, llvm::Module &M,
- RuntimeCheck &RC);
+ RuntimeCheck &RC, ExceptionHandler &EH);
// NameStr/DataStr are already-coerced STRING ptrs; Line is the statement's
// source line for runtime diagnostics. All methods null-check and bail;
diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h
index d914162..a7cead5 100644
--- a/include/cps/FunctionGen.h
+++ b/include/cps/FunctionGen.h
@@ -12,6 +12,8 @@
namespace cps {
+class ExceptionHandler;
+
// Array-parameter ABI: each rank-R array parameter is flattened into the
// data pointer followed by kBoundArgsPerDim i64 bounds per dimension.
inline constexpr unsigned kBoundArgsPerDim = 2; // lower, upper
@@ -51,6 +53,7 @@ private:
llvm::Module &Module;
llvm::IRBuilder<> &Builder;
TypeSystem &Types;
+ ExceptionHandler &Exceptions;
std::map<std::string, SymbolInfo> &Symbols;
bool &HadError;
@@ -65,9 +68,10 @@ public:
llvm::Module &M,
llvm::IRBuilder<> &B,
TypeSystem &TS,
+ ExceptionHandler &EH,
std::map<std::string, SymbolInfo> &Sym,
bool &HadErrorFlag)
- : Context(C), Module(M), Builder(B), Types(TS), Symbols(Sym), HadError(HadErrorFlag) {}
+ : Context(C), Module(M), Builder(B), Types(TS), Exceptions(EH), Symbols(Sym), HadError(HadErrorFlag) {}
llvm::Type *getLLVMType(const std::string &TypeName);
diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h
index 3652df0..2876a58 100644
--- a/include/cps/Lexer.h
+++ b/include/cps/Lexer.h
@@ -88,7 +88,12 @@ enum Token {
tok_class = -604,
tok_endclass = -605,
tok_public = -606,
- tok_private = -607
+ tok_private = -607,
+
+ tok_try = -700,
+ tok_catch = -701,
+ tok_endtry = -702,
+ tok_throw = -703
};
class Lexer {
diff --git a/include/cps/Parser.h b/include/cps/Parser.h
index 7344015..49a9791 100644
--- a/include/cps/Parser.h
+++ b/include/cps/Parser.h
@@ -40,6 +40,7 @@ class Parser {
std::optional<std::vector<std::unique_ptr<ExprAST>>> ParseExprList(char CloseDelim, bool AllowEmpty);
void syncToStatementStart();
std::unique_ptr<StmtAST> ParseIfStmt();
+ std::unique_ptr<StmtAST> ParseTryStmt();
std::unique_ptr<StmtAST> ParseWhileStmt();
std::unique_ptr<StmtAST> ParseRepeatStmt();
std::unique_ptr<StmtAST> ParseForStmt();
diff --git a/include/cps/RuntimeCheck.h b/include/cps/RuntimeCheck.h
index 51a07aa..d9a69b6 100644
--- a/include/cps/RuntimeCheck.h
+++ b/include/cps/RuntimeCheck.h
@@ -7,24 +7,18 @@
namespace cps {
+class ExceptionHandler;
+
class RuntimeCheck {
- llvm::Module &TheModule;
llvm::LLVMContext &TheContext;
llvm::IRBuilder<> &Builder;
-
- llvm::FunctionCallee PrintfFunc;
- llvm::FunctionCallee ExitFunc;
- llvm::Value *DivZeroMsg;
- llvm::Value *OutOfBoundsMsg;
- llvm::Value *EnumRangeMsg;
- llvm::Value *NullDerefMsg;
- llvm::Value *ArrayArgBoundsMsg;
-
- void setupExternalFunctions();
- void emitErrorAndExit(llvm::Value *Condition, llvm::Value *Msg, int Line);
+ ExceptionHandler &Exceptions;
public:
- RuntimeCheck(llvm::Module &M, llvm::LLVMContext &C, llvm::IRBuilder<> &B);
+ RuntimeCheck(llvm::LLVMContext &C, llvm::IRBuilder<> &B, ExceptionHandler &EH);
+
+ // Split the block, throwing an anonymous exception when Condition is true.
+ void emitCheck(llvm::Value *Condition);
void emitDivZeroCheck(llvm::Value *Divisor, int Line);
void emitIndexCheck(llvm::Value *Index, llvm::Value *Lower, llvm::Value *Upper, int Line);
@@ -33,7 +27,7 @@ public:
void emitEnumRangeCheck(llvm::Value *Ordinal, uint64_t ValueCount,
const std::string &TypeName, int Line);
- // Dereferencing an unset (null) pointer dies instead of segfaulting.
+ // Dereferencing an unset (null) pointer throws instead of segfaulting.
void emitNullDerefCheck(llvm::Value *Ptr, int Line);
// A bounded array parameter is a contract: the argument's runtime bounds
@@ -41,12 +35,6 @@ public:
void emitArrayArgBoundsCheck(llvm::Value *ActualLower, llvm::Value *ActualUpper,
int64_t DeclaredLower, int64_t DeclaredUpper, int Line);
- // 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 emitErrorAndExit(llvm::Value *Condition, llvm::Value *Msg,
- llvm::ArrayRef<llvm::Value *> FmtArgs);
};
}