diff options
| author | _ov4 <ov4@ov4.org> | 2026-07-26 17:42:08 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-07-26 17:42:08 +0000 |
| commit | a44f1246dbe24fc813a212a5ef3f5811b1d6402a (patch) | |
| tree | f0894af7daa0bac56471c4dae684ec87588c055d /lib/CodeGen | |
| parent | d65c296a0ad8d335aa076aab48a8b198fa934a2d (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.cc | 152 | ||||
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 35 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 59 | ||||
| -rw-r--r-- | lib/CodeGen/RuntimeCheck.cc | 17 |
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}); +} |
