aboutsummaryrefslogtreecommitdiff
path: root/lib/CodeGen
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-07-26 17:42:08 +0000
committer_ov4 <ov4@ov4.org>2026-07-26 17:42:08 +0000
commita44f1246dbe24fc813a212a5ef3f5811b1d6402a (patch)
treef0894af7daa0bac56471c4dae684ec87588c055d /lib/CodeGen
parentd65c296a0ad8d335aa076aab48a8b198fa934a2d (diff)
feat: whole arrays (and records) as FUNCTION/PROCEDURE parameters
- Parameter syntax lands end-to-end: ARRAY OF T (rank 1, bounds travel with the argument) and ARRAY[l:u,...] OF T (any rank, literal bounds as a checked contract). Element type and rank must match exactly. - Flattened ABI: each rank-R array parameter becomes a data pointer plus kBoundArgsPerDim i64 bounds per dimension. The callee rebuilds ArrayMetadata from those bounds (ArrayHandler::bindArrayParameter via a binder callback injected by CodeGen, so FunctionGen stays ignorant of the array subsystem); the checked-indexing pipeline is reused unchanged. - BYVAL (the CIE default) copies the whole buffer at callee entry (malloc+memcpy, never freed — consistent with the string policy) so callee writes stay invisible; BYREF binds the caller's buffer. - Bounded-parameter contract: compile-time comparison when the caller's bounds are constants, else a runtime [Fatal] check (RuntimeCheck::emitArrayArgBoundsCheck). - marshalCallArgs gains the array branch and a call-site line number (CallStmtAST now carries one); whole-array-to-scalar keeps a clear error, FUNCTION RETURNS ARRAY is a dedicated parse error. - Verified: BYREF fill/sort-style mutation, BYVAL copy invisibility, 2-D bounded params, ARRAY OF <record> with field writes through the parameter, bounds-contract mismatches at compile time and run time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'lib/CodeGen')
-rw-r--r--lib/CodeGen/ArrayHandler.cc152
-rw-r--r--lib/CodeGen/CodeGen.cc35
-rw-r--r--lib/CodeGen/FunctionGen.cc59
-rw-r--r--lib/CodeGen/RuntimeCheck.cc17
4 files changed, 229 insertions, 34 deletions
diff --git a/lib/CodeGen/ArrayHandler.cc b/lib/CodeGen/ArrayHandler.cc
index d21d91c..c647d58 100644
--- a/lib/CodeGen/ArrayHandler.cc
+++ b/lib/CodeGen/ArrayHandler.cc
@@ -91,8 +91,6 @@ void ArrayHandler::emitArrayDeclare(ArrayDeclareStmtAST *Stmt, CodeGen &CG) {
std::vector<Value*> Lows;
std::vector<Value*> Highs;
- std::vector<Value*> Dims;
- Value *TotalElements = ConstantInt::get(*TheContext, APInt(64, 1));
for (const auto &Pair : Stmt->getBounds()) {
Value *L = CG.emitExpr(Pair.first.get());
@@ -104,18 +102,11 @@ void ArrayHandler::emitArrayDeclare(ArrayDeclareStmtAST *Stmt, CodeGen &CG) {
Lows.push_back(L);
Highs.push_back(R);
-
- Value *Diff = Builder->CreateSub(R, L, Name + "_dim_diff");
- Value *DimSize = Builder->CreateAdd(Diff, ConstantInt::get(*TheContext, APInt(64, 1)), Name + "_dim_size");
- Dims.push_back(DimSize);
- TotalElements = Builder->CreateMul(TotalElements, DimSize, Name + "_total_elems");
}
- std::vector<Value*> Multipliers(Rank);
- Multipliers[Rank - 1] = ConstantInt::get(*TheContext, APInt(64, 1));
- for (int i = Rank - 2; i >= 0; --i) {
- Multipliers[i] = Builder->CreateMul(Multipliers[i + 1], Dims[i + 1], Name + "_mult");
- }
+ std::vector<Value*> Multipliers;
+ Value *TotalElements = nullptr;
+ computeDimsAndMultipliers(Name, Lows, Highs, Multipliers, TotalElements);
ArrayMetadata Meta;
Meta.Rank = Rank;
@@ -144,6 +135,30 @@ void ArrayHandler::emitArrayDeclare(ArrayDeclareStmtAST *Stmt, CodeGen &CG) {
CG.registerSymbol(Name, Alloca, ElemInfo->Name, true);
}
+void ArrayHandler::computeDimsAndMultipliers(const std::string &Name,
+ const std::vector<Value*> &Lows,
+ const std::vector<Value*> &Highs,
+ std::vector<Value*> &Multipliers,
+ Value *&TotalElements) {
+ int Rank = static_cast<int>(Lows.size());
+ std::vector<Value*> Dims;
+ TotalElements = ConstantInt::get(*TheContext, APInt(64, 1));
+
+ for (int I = 0; I < Rank; ++I) {
+ Value *Diff = Builder->CreateSub(Highs[I], Lows[I], Name + "_dim_diff");
+ Value *DimSize = Builder->CreateAdd(Diff, ConstantInt::get(*TheContext, APInt(64, 1)),
+ Name + "_dim_size");
+ Dims.push_back(DimSize);
+ TotalElements = Builder->CreateMul(TotalElements, DimSize, Name + "_total_elems");
+ }
+
+ Multipliers.assign(Rank, nullptr);
+ Multipliers[Rank - 1] = ConstantInt::get(*TheContext, APInt(64, 1));
+ for (int I = Rank - 2; I >= 0; --I) {
+ Multipliers[I] = Builder->CreateMul(Multipliers[I + 1], Dims[I + 1], Name + "_mult");
+ }
+}
+
bool ArrayHandler::emitCheckedIndices(const std::string &Name,
const std::vector<std::unique_ptr<ExprAST>> &IndexExprs,
int Line,
@@ -238,6 +253,119 @@ std::map<std::string, ArrayMetadata> ArrayHandler::exchangeTable(std::map<std::s
return NewTable;
}
+void ArrayHandler::bindArrayParameter(const std::string &Name,
+ const std::string &ElemTypeName,
+ Value *DataPtr,
+ const std::vector<Value*> &LBs,
+ const std::vector<Value*> &UBs,
+ bool MakeCopy,
+ CodeGen &CG) {
+ const TypeInfo *ElemInfo = Types.resolve(ElemTypeName);
+ if (!ElemInfo || !ElemInfo->LLVMType || ElemInfo->isVoid()) {
+ CG.reportError("Unknown array element type %s", ElemTypeName.c_str());
+ return;
+ }
+
+ std::vector<Value*> Multipliers;
+ Value *TotalElements = nullptr;
+ computeDimsAndMultipliers(Name, LBs, UBs, Multipliers, TotalElements);
+
+ ArrayMetadata Meta;
+ Meta.Rank = static_cast<int>(LBs.size());
+ Meta.ElementTypeName = ElemInfo->Name;
+ Meta.ElementType = ElemInfo->LLVMType;
+ Meta.ElementSizeC = Types.getSizeOfConstant(ElemInfo->LLVMType);
+ Meta.LowerBounds = LBs;
+ Meta.UpperBounds = UBs;
+ Meta.Multipliers = std::move(Multipliers);
+ ArrayTable[Name] = Meta;
+
+ Value *Ptr = DataPtr;
+ if (MakeCopy) {
+ // BYVAL: whole-buffer copy so callee writes stay invisible to the
+ // caller. The copy is malloc'd and never freed, consistent with the
+ // project-wide string policy.
+ Value *TotalBytes = Builder->CreateMul(TotalElements, Meta.ElementSizeC,
+ Name + "_copy_bytes");
+ Value *Copy = Builder->CreateCall(MallocFunc, TotalBytes, Name + "_copy");
+ FunctionType *MemcpyType = FunctionType::get(
+ PointerType::getUnqual(*TheContext),
+ {PointerType::getUnqual(*TheContext), PointerType::getUnqual(*TheContext),
+ Type::getInt64Ty(*TheContext)},
+ false);
+ FunctionCallee MemcpyFunc = TheModule->getOrInsertFunction("memcpy", MemcpyType);
+ Builder->CreateCall(MemcpyFunc, {Copy, DataPtr, TotalBytes});
+ Ptr = Copy;
+ }
+
+ Function *TheFunction = Builder->GetInsertBlock()->getParent();
+ AllocaInst *Alloca = CG.CreateEntryBlockAlloca(TheFunction, PointerType::getUnqual(*TheContext), Name);
+ Builder->CreateStore(Ptr, Alloca);
+
+ CG.registerSymbol(Name, Alloca, ElemInfo->Name, true);
+}
+
+bool ArrayHandler::emitArrayArgument(ExprAST *ArgExpr,
+ const std::string &ElemTypeName,
+ int Rank,
+ const std::vector<std::pair<int64_t, int64_t>> &DeclaredBounds,
+ const std::string &Callee,
+ int Line,
+ CodeGen &CG,
+ std::vector<Value*> &Out) {
+ auto *Var = dynamic_cast<VariableExprAST*>(ArgExpr);
+ const ArrayMetadata *Meta = Var ? getMetadata(Var->getName()) : nullptr;
+ if (!Meta) {
+ CG.reportError("Argument to %s must be a whole array variable (ARRAY OF %s)",
+ Callee.c_str(), ElemTypeName.c_str());
+ return false;
+ }
+ if (Meta->ElementTypeName != ElemTypeName) {
+ CG.reportError("Array argument %s to %s must have element type %s (got %s)",
+ Var->getName().c_str(), Callee.c_str(),
+ ElemTypeName.c_str(), Meta->ElementTypeName.c_str());
+ return false;
+ }
+ if (Meta->Rank != Rank) {
+ CG.reportError("Array argument %s to %s must have %d dimension%s (got %d)",
+ Var->getName().c_str(), Callee.c_str(),
+ Rank, Rank == 1 ? "" : "s", Meta->Rank);
+ return false;
+ }
+
+ // Bounded parameters are a contract: verified at compile time when the
+ // caller's bounds are constants (the common case), at run time otherwise.
+ for (size_t D = 0; D < DeclaredBounds.size(); ++D) {
+ int64_t DeclLo = DeclaredBounds[D].first;
+ int64_t DeclHi = DeclaredBounds[D].second;
+ auto *ActLo = dyn_cast<ConstantInt>(Meta->LowerBounds[D]);
+ auto *ActHi = dyn_cast<ConstantInt>(Meta->UpperBounds[D]);
+ if (ActLo && ActHi) {
+ if (ActLo->getSExtValue() != DeclLo || ActHi->getSExtValue() != DeclHi) {
+ CG.reportError("Array argument %s to %s has bounds [%lld:%lld] but the parameter declares [%lld:%lld]",
+ Var->getName().c_str(), Callee.c_str(),
+ static_cast<long long>(ActLo->getSExtValue()),
+ static_cast<long long>(ActHi->getSExtValue()),
+ static_cast<long long>(DeclLo),
+ static_cast<long long>(DeclHi));
+ return false;
+ }
+ } else {
+ RuntimeChecker.emitArrayArgBoundsCheck(Meta->LowerBounds[D], Meta->UpperBounds[D],
+ DeclLo, DeclHi, Line);
+ }
+ }
+
+ Value *DataPtr = getArrayBasePointer(Var->getName());
+ if (!DataPtr) return false;
+ Out.push_back(DataPtr);
+ for (int D = 0; D < Rank; ++D) {
+ Out.push_back(Meta->LowerBounds[D]);
+ Out.push_back(Meta->UpperBounds[D]);
+ }
+ return true;
+}
+
bool ArrayHandler::tryEmitArrayOutput(ExprAST *Expr, CodeGen &CG) {
std::string Name;
const std::vector<std::unique_ptr<ExprAST>> *IndexExprs = nullptr;
diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc
index 62a6f1e..64e1b9f 100644
--- a/lib/CodeGen/CodeGen.cc
+++ b/lib/CodeGen/CodeGen.cc
@@ -90,6 +90,15 @@ CodeGen::CodeGen() {
StrConvHandler = std::make_unique<StringConversionHandler>(*TheContext, *Builder, *TheModule);
Files = std::make_unique<FileHandler>(*TheContext, *Builder, *TheModule, *RuntimeChecker);
+ // Array parameters are bound into ArrayHandler through this inversion so
+ // FunctionGen never sees the array subsystem.
+ FuncGen->setArrayParamBinder(
+ [this](const ParamDecl &P, Value *DataPtr,
+ const std::vector<Value*> &LBs, const std::vector<Value*> &UBs) {
+ Arrays->bindArrayParameter(P.Name, P.TypeName, DataPtr, LBs, UBs,
+ /*MakeCopy=*/P.Mode == PassMode::ByVal, *this);
+ });
+
SetupExternalFunctions();
}
@@ -714,7 +723,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
}
std::vector<Value*> Args;
- if (!marshalCallArgs(Name, Call->getArgs(), Args)) return nullptr;
+ if (!marshalCallArgs(Name, Call->getArgs(), Call->getLine(), Args)) return nullptr;
return FuncGen->emitCallExpr(Call, Args);
}
@@ -740,6 +749,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) {
// 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,
+ int Line,
std::vector<Value*> &Out) {
const FuncSig *Sig = FuncGen->getSignature(Callee);
if (!Sig) {
@@ -752,17 +762,26 @@ bool CodeGen::marshalCallArgs(const std::string &Callee,
}
for (size_t i = 0; i < ArgExprs.size(); ++i) {
- const std::string &TypeName = Sig->Params[i].first;
- bool IsByRef = Sig->Params[i].second;
+ const ParamSig &P = Sig->Params[i];
+ const std::string &TypeName = P.TypeName;
+ bool IsByRef = P.IsByRef;
ExprAST *ArgExpr = ArgExprs[i].get();
- // Parameters can never be arrays, so a whole-array argument is always
- // a user error (and would smash the data pointer if let through).
+ if (P.IsArray) {
+ if (!Arrays->emitArrayArgument(ArgExpr, P.TypeName, P.Rank, P.DeclaredBounds,
+ Callee, Line, *this, Out)) {
+ return false;
+ }
+ continue;
+ }
+
+ // A whole-array argument against a scalar parameter is always a user
+ // error (and would smash the data pointer if let through).
if (auto *Var = dynamic_cast<VariableExprAST*>(ArgExpr)) {
const SymbolInfo *Info = getSymbolInfo(Var->getName());
if (Info && Info->IsArray) {
- reportError("Cannot pass whole array %s to %s; pass an element like %s[i]",
- Var->getName().c_str(), Callee.c_str(), Var->getName().c_str());
+ reportError("Cannot pass whole array %s to %s; the parameter is not an array (declare it ARRAY OF %s)",
+ Var->getName().c_str(), Callee.c_str(), TypeName.c_str());
return false;
}
}
@@ -959,7 +978,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) {
if (auto *Call = dynamic_cast<CallStmtAST*>(Stmt)) {
std::vector<Value*> Args;
- if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Args)) return;
+ if (!marshalCallArgs(Call->getCallee(), Call->getArgs(), Call->getLine(), Args)) return;
FuncGen->emitCallStmt(Call, Args);
return;
}
diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc
index cbaee75..77a37ec 100644
--- a/lib/CodeGen/FunctionGen.cc
+++ b/lib/CodeGen/FunctionGen.cc
@@ -42,6 +42,24 @@ void FunctionGen::createArgumentAllocas(Function *F, const std::vector<ParamDecl
++AI;
ArgVal->setName(P.Name);
+ if (P.IsArray) {
+ // Data pointer + kBoundArgsPerDim bounds per dimension; binding
+ // into the array subsystem is delegated to the injected binder.
+ std::vector<Value*> LBs, UBs;
+ for (int D = 1; D <= P.Rank; ++D) {
+ Value *LB = &(*AI);
+ ++AI;
+ LB->setName(P.Name + kLowerBoundSuffix + std::to_string(D));
+ Value *UB = &(*AI);
+ ++AI;
+ UB->setName(P.Name + kUpperBoundSuffix + std::to_string(D));
+ LBs.push_back(LB);
+ UBs.push_back(UB);
+ }
+ if (BindArrayParam) BindArrayParam(P, ArgVal, LBs, UBs);
+ continue;
+ }
+
if (P.Mode == PassMode::ByRef) {
Symbols[P.Name] = {ArgVal, P.TypeName, false};
continue;
@@ -86,18 +104,27 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
std::vector<Type*> ArgTypes;
for (const ParamDecl &P : Proto->getParams()) {
+ ParamSig PS;
+ PS.TypeName = P.TypeName;
+ PS.IsByRef = P.Mode == PassMode::ByRef;
+ PS.IsArray = P.IsArray;
+ PS.Rank = P.Rank;
+ PS.DeclaredBounds = P.DeclaredBounds;
+
if (P.IsArray) {
- fprintf(stderr, "Error: Array parameters are not implemented yet (parameter '%s' of %s)\n",
- P.Name.c_str(), Proto->getName().c_str());
- HadError = true;
- return nullptr;
- }
- Type *T = getLLVMType(P.TypeName);
- if (P.Mode == PassMode::ByRef) {
- T = T->getPointerTo();
+ getLLVMType(P.TypeName); // diagnose an unknown element type
+ ArgTypes.push_back(PointerType::getUnqual(Context)); // data pointer
+ for (unsigned D = 0; D < static_cast<unsigned>(P.Rank) * kBoundArgsPerDim; ++D) {
+ ArgTypes.push_back(Type::getInt64Ty(Context));
+ }
+ } else {
+ Type *T = getLLVMType(P.TypeName);
+ if (PS.IsByRef) {
+ T = T->getPointerTo();
+ }
+ ArgTypes.push_back(T);
}
- ArgTypes.push_back(T);
- Sig.Params.emplace_back(P.TypeName, P.Mode == PassMode::ByRef);
+ Sig.Params.push_back(std::move(PS));
}
Type *RetType = getLLVMType(Proto->getReturnType());
@@ -117,10 +144,14 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) {
// wins and the second is rejected by emitFunctionDef.
Signatures.emplace(Proto->getName(), std::move(Sig));
- unsigned Idx = 0;
- for (auto &Arg : F->args()) {
- if (Idx < Proto->getParams().size()) {
- Arg.setName(Proto->getParams()[Idx++].Name);
+ Function::arg_iterator AI = F->arg_begin();
+ for (const ParamDecl &P : Proto->getParams()) {
+ if (AI == F->arg_end()) break;
+ (AI++)->setName(P.Name);
+ for (int D = 1; P.IsArray && D <= P.Rank && AI != F->arg_end(); ++D) {
+ (AI++)->setName(P.Name + kLowerBoundSuffix + std::to_string(D));
+ if (AI != F->arg_end())
+ (AI++)->setName(P.Name + kUpperBoundSuffix + std::to_string(D));
}
}
return F;
diff --git a/lib/CodeGen/RuntimeCheck.cc b/lib/CodeGen/RuntimeCheck.cc
index e6d4bf8..40304fd 100644
--- a/lib/CodeGen/RuntimeCheck.cc
+++ b/lib/CodeGen/RuntimeCheck.cc
@@ -27,6 +27,9 @@ void RuntimeCheck::setupExternalFunctions() {
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) {
@@ -86,3 +89,17 @@ void RuntimeCheck::emitNullDerefCheck(Value *Ptr, int Line) {
"ptr_is_null");
emitErrorAndExit(IsNull, NullDerefMsg, Line);
}
+
+void RuntimeCheck::emitArrayArgBoundsCheck(Value *ActualLower, Value *ActualUpper,
+ int64_t DeclaredLower, int64_t DeclaredUpper,
+ int Line) {
+ Value *DeclLo = ConstantInt::get(TheContext, APInt(64, static_cast<uint64_t>(DeclaredLower), true));
+ Value *DeclHi = ConstantInt::get(TheContext, APInt(64, static_cast<uint64_t>(DeclaredUpper), true));
+ Value *LoDiff = Builder.CreateICmpNE(ActualLower, DeclLo, "arg_lo_ne");
+ 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});
+}