diff options
| author | _ov4 <ov4@ov4.org> | 2026-07-26 12:07:51 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-07-26 12:07:51 +0000 |
| commit | 4a25b279d8f6c662fa8152cfe42e038d1ea06901 (patch) | |
| tree | 58223f2db133a10827e0a6d0c18c33f04bbc2527 | |
| parent | efc86b5c7625cf4c7074319f42f68807a25bde9d (diff) | |
refactor: keep pseudocode signatures, single call-marshalling path
emitPrototype now records each FUNCTION/PROCEDURE's pseudocode signature
(FuncSig: return type + per-param type/BYREF flag). CodeGen::
marshalCallArgs replaces the two verbatim-duplicated marshalling blocks
and dispatches on the declared signature instead of inferring BYREF from
pointer-typed LLVM params: BYVAL STRING params now receive the string
data pointer (no more runtime garbage), and BYVAL args are coerced to
the declared type (INTEGER->REAL args no longer emit invalid IR).
Top-level prototypes are pre-registered in compile(), so calls may
precede definitions; GenerateCall no longer fabricates i64 externs for
unknown names - an unregistered callee is a compile error. OUTPUT
reports an error when it cannot infer the expression type instead of
printing with the INTEGER format.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| -rw-r--r-- | AGENTS/CONVENTIONS.md | 19 | ||||
| -rw-r--r-- | AGENTS/PITFALLS.md | 3 | ||||
| -rw-r--r-- | AGENTS/SUBSYSTEMS.md | 21 | ||||
| -rw-r--r-- | include/cps/CodeGen.h | 3 | ||||
| -rw-r--r-- | include/cps/FunctionGen.h | 12 | ||||
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 112 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 24 |
7 files changed, 107 insertions, 87 deletions
diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index c05734f..e09b6ee 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -58,16 +58,15 @@ non-zero when any flag is set. The parser still drops a failed statement, skips token, and continues — later statements can be swallowed during recovery, but the failure is always diagnosed and reflected in the exit code. -**BYREF is a pointer-typed parameter, inferred structurally at call sites.** -`FunctionGen` emits pointer params for BYREF; call sites (in CodeGen, duplicated for -call-expr and call-stmt) treat *any* pointer-typed callee param as BYREF and require the -argument to be a bare variable whose alloca is passed. The callee side, however, binds -by the *declared* flag. Since STRING itself lowers to `ptr`, the two sides disagree for -STRING: the caller always passes the alloca, but a declared-BYVAL STRING param (the -default) copies that alloca *address* and treats it as the string data pointer — reading -garbage at runtime. Only declared-BYREF STRING parameters work, and STRING arguments can -never be literals/expressions. Function names get no mangling — a pseudocode -`FUNCTION printf` collides with libc. +**Calls dispatch on the declared signature.** `FunctionGen::emitPrototype` records every +function's pseudocode signature (`FuncSig`: return type name + per-param type name and +BYREF flag); `CodeGen::marshalCallArgs` — the single call-marshalling path for both +call-expr and call-stmt — requires BYREF arguments to be bare variables (their alloca is +passed) and coerces BYVAL arguments to the declared parameter type. STRING works on both +sides in both modes. Top-level prototypes are pre-registered before statement emission, +so call-before-definition is fine; a call with no registered signature is a compile +error. Function names get no mangling — a pseudocode `FUNCTION printf` collides with +libc. **Builtins are plain identifiers backed by one table.** All 11 builtins (LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM) parse as diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index b068dee..393f507 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -20,8 +20,7 @@ test programs. bits. - **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check it at any new call site) and internally defaults to INTEGER for arithmetic binaries and - unknown-function calls; OUTPUT substitutes INTEGER for a nullptr result — so wrong - inference prints garbage instead of erroring. + unknown-function calls; OUTPUT reports an error for a nullptr result. - The lexer emits no newline tokens; the grammar is purely keyword-delimited. Bare `RETURN` is only recognized right before ENDIF/ELSE/ENDFUNCTION/ENDPROCEDURE. - Invalid modules still print their IR, but `verifyModule` runs at the end of diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index 2910bb3..f26cddc 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -6,9 +6,10 @@ known bugs: `PITFALLS.md`.) **Arrays** (`ArrayHandler`): a DECLAREd array is one malloc'd flat buffer (memset to zero), row-major, any rank, any bounds. Bounds and multipliers are *compile-time SSA values* cached in `ArrayMetadata` (global `ArrayTable` map, keyed by name). Every access -goes through `getMetadata` → per-dimension `emitIndexCheck` → `computeFlatIndex` → -`getElementPointer`; never hand-roll indexing. STRING elements read as null until -written; every read path must replicate the null-guard `select` to the empty string. +goes through `emitCheckedIndices` (validates the index count for the caller's semantics, +then per-dimension coerce + `emitIndexCheck`) → `computeFlatIndex` → `loadElement` +(load + the STRING null-guard `select` to the empty string); never hand-roll indexing +or element loads. `OUTPUT arr` (and partially-indexed accesses) is intercepted by `ArrayHandler::tryEmitArrayOutput` — the first branch of OUTPUT dispatch, emitting nested print loops — so any rework of OUTPUT must preserve that interception. @@ -17,12 +18,14 @@ nested print loops — so any rework of OUTPUT must preserve that interception. name. Params are `tuple<name, typeString, isByRef>`. BYVAL args are copied into entry-block allocas; BYREF args bind the incoming pointer directly as the variable's storage. Missing RETURNS defaults the return type to INTEGER; procedures use "VOID". -**Definitions must precede first call**: calling a not-yet-defined function fabricates -an extern declaration (i64 return, param types guessed from the arguments), and when the -real definition arrives, `emitFunctionDef` reuses that declaration by name without -checking the signature — the body attaches to the wrong prototype and the module is -invalid. Body emission is inverted: CodeGen injects a `StmtEmitter` callback, and must -restore its own insert point afterwards. +`emitPrototype` records each function's pseudocode signature in a `FuncSig` map +(`FunctionGen::getSignature`); `CodeGen::marshalCallArgs` dispatches call arguments on +those *declared* param types and BYREF flags (BYVAL args are coerced to the declared +type), and `getExprTypeInfo` reads return types from the same map. Top-level prototypes +are pre-registered at the start of `compile()`, so calls may precede definitions; +calling a name with no signature is a compile error (no extern is fabricated). Body +emission is inverted: CodeGen injects a `StmtEmitter` callback, and must restore its +own insert point afterwards. **Runtime checks** (`RuntimeCheck`): only array bounds checking is actually wired up. `emitDivZeroCheck` exists but has **zero call sites** — division by zero (for `/`, DIV diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 268ba7e..b1d3831 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -69,6 +69,9 @@ class CodeGen { const SymbolInfo *getSymbolInfo(const std::string &Name) const; const TypeInfo *getExprTypeInfo(ExprAST *Expr) const; llvm::Value *coerceValueToType(llvm::Value *Val, const TypeInfo *TargetInfo); + bool marshalCallArgs(const std::string &Callee, + const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, + std::vector<llvm::Value*> &Out); void emitDeclareStmt(DeclareStmtAST *Stmt); void emitOutputValue(llvm::Value *Val, const TypeInfo *TypeInfo, bool AppendNewline = true); diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index 1dc3901..f55db1b 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -11,6 +11,14 @@ namespace cps { +// Pseudocode-level signature of a FUNCTION/PROCEDURE, kept so call sites can +// dispatch on declared parameter types and BYREF flags instead of inferring +// them back from LLVM types. +struct FuncSig { + std::string ReturnTypeName; + std::vector<std::pair<std::string, bool>> Params; // (TypeName, IsByRef) +}; + class FunctionGen { llvm::LLVMContext &Context; llvm::Module &Module; @@ -21,6 +29,8 @@ class FunctionGen { std::map<std::string, SymbolInfo> &Symbols; bool &HadError; + std::map<std::string, FuncSig> Signatures; + void createArgumentAllocas(llvm::Function *F, const std::vector<std::tuple<std::string, std::string, bool>> &Args); public: @@ -35,6 +45,8 @@ public: llvm::Type *getLLVMType(const std::string &TypeName); + const FuncSig *getSignature(const std::string &Name) const; + llvm::Function *emitPrototype(PrototypeAST *Proto); llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function<void(StmtAST*)> &StmtEmitter); diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 581e4a9..1c019d9 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -211,6 +211,9 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) const { if (const BuiltinInfo *B = findBuiltin(Name)) { return resolveType(B->ReturnTypeName); } + if (const FuncSig *Sig = FuncGen->getSignature(Name)) { + return resolveType(Sig->ReturnTypeName); + } Function *CalleeF = TheModule->getFunction(Name); if (!CalleeF) return resolveType("INTEGER"); @@ -416,6 +419,14 @@ void CodeGen::compile(const std::vector<std::unique_ptr<StmtAST>> &Statements) { BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", F); Builder->SetInsertPoint(BB); + // Pre-register every top-level FUNCTION/PROCEDURE prototype so calls may + // precede definitions. + for (const auto &Stmt : Statements) { + if (auto *FuncDef = dynamic_cast<FunctionDefAST*>(Stmt.get())) { + FuncGen->emitPrototype(FuncDef->getProto()); + } + } + for (const auto &Stmt : Statements) { emitStmt(Stmt.get()); } @@ -578,39 +589,54 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { return StrConvHandler->emitStrToNum(emitExpr(Call->getArgs()[0].get()), true); } - Function *CalleeF = TheModule->getFunction(Call->getCallee()); std::vector<Value*> Args; + if (!marshalCallArgs(Name, Call->getArgs(), Args)) return nullptr; + return FuncGen->emitCallExpr(Call, Args); + } - for (unsigned i = 0; i < Call->getArgs().size(); ++i) { - ExprAST *ArgExpr = Call->getArgs()[i].get(); - bool IsByRef = false; + return nullptr; +} - if (CalleeF && i < CalleeF->arg_size()) { - if (CalleeF->getArg(i)->getType()->isPointerTy()) { - IsByRef = true; - } - } +// Collects the arguments for a call to a user FUNCTION/PROCEDURE, dispatching +// on the *declared* signature: BYREF parameters take the variable's alloca, +// BYVAL parameters are evaluated and coerced to the declared parameter type. +bool CodeGen::marshalCallArgs(const std::string &Callee, + const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, + std::vector<Value*> &Out) { + const FuncSig *Sig = FuncGen->getSignature(Callee); + if (!Sig) { + reportError("Call to undefined function %s", Callee.c_str()); + return false; + } + if (ArgExprs.size() != Sig->Params.size()) { + reportError("Incorrect # arguments passed to %s", Callee.c_str()); + return false; + } - if (IsByRef) { - if (auto *Var = dynamic_cast<VariableExprAST*>(ArgExpr)) { - Value *Ptr = getNamedValue(Var->getName()); - if (!Ptr) { - reportError("Unknown variable %s in BYREF call", Var->getName().c_str()); - return nullptr; - } - Args.push_back(Ptr); - } else { - reportError("BYREF argument must be a variable."); - return nullptr; - } - } else { - Args.push_back(emitExpr(ArgExpr)); + for (size_t i = 0; i < ArgExprs.size(); ++i) { + const std::string &TypeName = Sig->Params[i].first; + bool IsByRef = Sig->Params[i].second; + ExprAST *ArgExpr = ArgExprs[i].get(); + + if (IsByRef) { + auto *Var = dynamic_cast<VariableExprAST*>(ArgExpr); + if (!Var) { + reportError("BYREF argument to %s must be a variable", Callee.c_str()); + return false; + } + Value *Ptr = getNamedValue(Var->getName()); + if (!Ptr) { + reportError("Unknown variable %s in BYREF call", Var->getName().c_str()); + return false; } + Out.push_back(Ptr); + } else { + Value *V = coerceValueToType(emitExpr(ArgExpr), resolveType(TypeName)); + if (!V) return false; + Out.push_back(V); } - return FuncGen->emitCallExpr(Call, Args); } - - return nullptr; + return true; } void CodeGen::emitIfStmt(IfStmtAST *Stmt) { @@ -775,35 +801,8 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } if (auto *Call = dynamic_cast<CallStmtAST*>(Stmt)) { - Function *CalleeF = TheModule->getFunction(Call->getCallee()); std::vector<Value*> Args; - - for (unsigned i = 0; i < Call->getArgs().size(); ++i) { - ExprAST *ArgExpr = Call->getArgs()[i].get(); - bool IsByRef = false; - - if (CalleeF && i < CalleeF->arg_size()) { - if (CalleeF->getArg(i)->getType()->isPointerTy()) { - IsByRef = true; - } - } - - if (IsByRef) { - if (auto *Var = dynamic_cast<VariableExprAST*>(ArgExpr)) { - Value *Ptr = getNamedValue(Var->getName()); - if (!Ptr) { - reportError("Unknown variable %s in BYREF call", Var->getName().c_str()); - return; - } - Args.push_back(Ptr); - } else { - reportError("BYREF argument must be a variable."); - return; - } - } else { - Args.push_back(emitExpr(ArgExpr)); - } - } + if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Args)) return; FuncGen->emitCallStmt(Call, Args); return; } @@ -899,7 +898,10 @@ void CodeGen::emitStmt(StmtAST *Stmt) { Value *Val = emitExpr(Out->getExpr()); const TypeInfo *TypeInfo = getExprTypeInfo(Out->getExpr()); - if (!TypeInfo) TypeInfo = resolveType("INTEGER"); + if (!TypeInfo) { + reportError("Cannot infer the type of the OUTPUT expression"); + return; + } emitOutputValue(Val, TypeInfo, true); return; } diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 0a22278..57454b1 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -41,7 +41,15 @@ void FunctionGen::createArgumentAllocas(Function *F, const std::vector<std::tupl } } +const FuncSig *FunctionGen::getSignature(const std::string &Name) const { + auto It = Signatures.find(Name); + return It == Signatures.end() ? nullptr : &It->second; +} + Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { + FuncSig Sig; + Sig.ReturnTypeName = Proto->getReturnType(); + std::vector<Type*> ArgTypes; for (const auto &Arg : Proto->getArgs()) { Type *T = getLLVMType(std::get<1>(Arg)); @@ -49,7 +57,9 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { T = T->getPointerTo(); } ArgTypes.push_back(T); + Sig.Params.emplace_back(std::get<1>(Arg), std::get<2>(Arg)); } + Signatures[Proto->getName()] = std::move(Sig); Type *RetType = getLLVMType(Proto->getReturnType()); FunctionType *FT = FunctionType::get(RetType, ArgTypes, false); @@ -112,20 +122,12 @@ Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, static Value *GenerateCall(llvm::Module &Module, llvm::IRBuilder<> &Builder, - llvm::LLVMContext &Context, const std::string &CalleeName, const std::vector<llvm::Value*> &Args, bool &HadError) { Function *CalleeF = Module.getFunction(CalleeName); if (!CalleeF) { - std::vector<Type*> ArgTypes; - for (auto *Val : Args) ArgTypes.push_back(Val->getType()); - FunctionType *FT = FunctionType::get(Type::getInt64Ty(Context), ArgTypes, false); - CalleeF = Function::Create(FT, Function::ExternalLinkage, CalleeName, &Module); - } - - if (CalleeF->arg_size() != Args.size()) { - fprintf(stderr, "Error: Incorrect # arguments passed to %s\n", CalleeName.c_str()); + fprintf(stderr, "Error: Call to undefined function %s\n", CalleeName.c_str()); HadError = true; return nullptr; } @@ -138,11 +140,11 @@ static Value *GenerateCall(llvm::Module &Module, } llvm::Value *FunctionGen::emitCallExpr(CallExprAST *Call, const std::vector<llvm::Value*> &Args) { - return GenerateCall(Module, Builder, Context, Call->getCallee(), Args, HadError); + return GenerateCall(Module, Builder, Call->getCallee(), Args, HadError); } void FunctionGen::emitCallStmt(CallStmtAST *Call, const std::vector<llvm::Value*> &Args) { - GenerateCall(Module, Builder, Context, Call->getCallee(), Args, HadError); + GenerateCall(Module, Builder, Call->getCallee(), Args, HadError); } void FunctionGen::emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal) { |
