From dc33cd2faf6fa52486c02e86038c8f8c07c0fe27 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sat, 17 Oct 2026 15:06:44 +0000 Subject: feat(codegen): declare and call EXTERN C functions Variadic arguments must be C values and receive C default argument promotions; narrow integer parameters carry sign/zero extension. Co-Authored-By: Claude Opus 5.5 --- include/cps/CodeGen.h | 1 + include/cps/FunctionGen.h | 8 ++++ lib/CodeGen/CodeGen.cc | 30 ++++++++++++- lib/CodeGen/Designator.cc | 2 +- lib/CodeGen/ExpressionGen.cc | 2 +- lib/CodeGen/FunctionGen.cc | 101 ++++++++++++++++++++++++++++++++++++++++++- lib/CodeGen/StatementGen.cc | 1 + 7 files changed, 141 insertions(+), 4 deletions(-) diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 04bf25c..d81a82c 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -155,6 +155,7 @@ class CodeGen { const std::vector> &ArgExprs, int Line); const FuncSig *resolveCall(const std::string &Name, const std::vector> &Args); + llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position); bool marshalCallArgs(const FuncSig &Signature, const std::vector> &ArgExprs, int Line, diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index c168e1c..5eb0f82 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -43,6 +43,12 @@ struct FuncSig { std::string ReturnTypeName; std::vector Params; size_t OwnerUnit = 0; + // EXTERN "C": called through its declared C type, which may differ from a + // runtime declaration of the same libc symbol. + bool IsExternal = false; + bool IsVariadic = false; + llvm::FunctionType *ExternType = nullptr; + llvm::AttributeList ExternAttrs; }; class FunctionGen { @@ -103,10 +109,12 @@ public: const FuncSig *getSignatureByLink(const std::string &Name) const; llvm::Function *emitPrototype(PrototypeAST *Proto); + bool emitExternPrototype(PrototypeAST *Proto); llvm::Function *emitFunctionDef(FunctionDefAST *FuncAST, const std::function &StmtEmitter, unsigned ScopeID); llvm::Value *emitCall(const std::string &Callee, const std::vector &Args); + llvm::Value *emitCall(const FuncSig &Sig, const std::vector &Args); void emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal); }; diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index 92a52e8..7abdad7 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -215,6 +215,9 @@ void CodeGen::compile(const std::vector &Sources) { !sameIdentifier(F->getProto()->getReturnType(), "INTEGER")) reportError("MAIN must be a FUNCTION with no parameters returning INTEGER"); } + } else if (auto *X = dynamic_cast(S.get())) { + Reserve(X->getProto()->getName(), true); + } else if (dynamic_cast(S.get())) { } else if (auto *E = dynamic_cast(S.get())) { TypeName = E->getName(); for (const auto &V : E->getValueNames()) Reserve(V); @@ -285,6 +288,8 @@ void CodeGen::compile(const std::vector &Sources) { SaveAndRestore Location(DiagnosticLine, Stmt->getLine()); if (auto *FuncDef = dynamic_cast(Stmt.get())) FuncGen->emitPrototype(FuncDef->getProto()); + if (auto *Extern = dynamic_cast(Stmt.get())) + FuncGen->emitExternPrototype(Extern->getProto()); if (auto *Class = dynamic_cast(Stmt.get())) { for (const auto &Method : Class->getMethods()) { SaveAndRestore MethodLocation(DiagnosticLine, Method->getLine()); @@ -365,12 +370,18 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature, SaveAndRestore Location(DiagnosticLine, Line ? Line : DiagnosticLine); const FuncSig *Sig = &Signature; const std::string &Callee = Sig->Name; - if (ArgExprs.size() != Sig->Params.size()) { + if (Sig->IsVariadic ? ArgExprs.size() < Sig->Params.size() : ArgExprs.size() != Sig->Params.size()) { reportError("Incorrect # arguments passed to %s", Callee.c_str()); return false; } for (size_t i = 0; i < ArgExprs.size(); ++i) { + if (i >= Sig->Params.size()) { + Value *V = emitVariadicArgument(ArgExprs[i].get(), Callee, i + 1); + if (!V) return false; + Out.push_back(V); + continue; + } const ParamSig &P = Sig->Params[i]; const std::string &TypeName = P.TypeName; bool IsByRef = P.IsByRef; @@ -424,6 +435,23 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature, return true; } +// C default argument promotions for the variadic part of an EXTERN call; only +// C values are accepted, matching the explicit-cast rule for C/pseudocode. +Value *CodeGen::emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position) { + const TypeInfo *Info = isWholeArrayVar(Expr) ? nullptr : getExprTypeInfo(Expr); + if (!Info || !Info->isC() || Info->isVoid()) { + reportError("Variadic argument %zu to %s must be a C value; cast it to a C type", Position, Callee.c_str()); + return nullptr; + } + Value *V = emitExpr(Expr); + if (!V || Info->Kind != TypeKind::CScalar) return V; + if (Info->isReal()) { + const TypeInfo *Double = resolveType("C_DOUBLE"); + return Info->CRank < Double->CRank ? convertNumericValue(V, Info, Double) : V; + } + return convertNumericValue(V, Info, Types->promoteCScalar(Info)); +} + // Resolve using static source types only; argument evaluation belongs to marshalling. const FuncSig *CodeGen::resolveCall(const std::string &Name, const std::vector> &Args) { diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index 467f788..5fb0ed3 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -116,7 +116,7 @@ Value *CodeGen::emitMethodCall(const std::string &Class, Value *Receiver, } std::vector Args = {Receiver}; if (!marshalCallArgs(*Sig, ArgExprs, Line, Args)) return nullptr; - return FuncGen->emitCall(Sig->LinkName, Args); + return FuncGen->emitCall(*Sig, Args); } // Resolves a designator (or legacy variable/array-access node) to the address diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index b667e6a..79c5fb0 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -301,7 +301,7 @@ Value *CodeGen::emitCall(const std::string &Callee, std::vector Args; const FuncSig *Sig = resolveCall(Name, ArgExprs); if (!Sig || !marshalCallArgs(*Sig, ArgExprs, Line, Args)) return nullptr; - return FuncGen->emitCall(Sig->LinkName, Args); + return FuncGen->emitCall(*Sig, Args); } Value *CodeGen::emitAllocationSize(ExprAST *Expr) { diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index b4b37d3..3937e01 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -127,7 +127,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { return nullptr; } if (Proto->getOwnerClass().empty()) { - if (isReservedRuntimeName(Proto->getName())) { + if (!Proto->isExternal() && isReservedRuntimeName(Proto->getName())) { reportError("'%s' is a reserved runtime symbol and cannot be a FUNCTION/PROCEDURE name", Proto->getName().c_str()); return nullptr; @@ -231,6 +231,96 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { return F; } +// The prototype names the exact C symbol; parameters and the result must be C +// types. Narrow integers carry the C ABI's sign/zero extension attributes. +bool FunctionGen::emitExternPrototype(PrototypeAST *Proto) { + if (PrototypeLinks.count(Proto)) return true; + const std::string &Name = Proto->getName(); + if (sameIdentifier(Name, "MAIN")) { + reportError("MAIN cannot be declared EXTERN"); + return false; + } + if (CodeGen::isBuiltinName(Name)) { + reportError("'%s' is a built-in function and cannot be redefined", Name.c_str()); + return false; + } + if (Types.lookupEnumConstant(Name) || Types.resolve(Name)) { + reportError("'%s' is already a type or enum value name", Name.c_str()); + return false; + } + + auto Extension = [](const TypeInfo *Info) { + if (Info->Kind != TypeKind::CScalar || !Info->LLVMType->isIntegerTy() || + Info->LLVMType->getIntegerBitWidth() >= 32) + return Attribute::None; + return Info->CSigned ? Attribute::SExt : Attribute::ZExt; + }; + + FuncSig Sig; + Sig.OwnerUnit = CurrentUnit; + Sig.Name = Name; + Sig.LinkName = Name; + Sig.Prototype = Proto; + Sig.ReturnTypeName = Proto->getReturnType(); + Sig.IsExternal = true; + Sig.IsVariadic = Proto->isVariadic(); + + std::vector ArgTypes; + AttrBuilder RetAttrs(Context); + std::vector ParamAttrs; + for (size_t I = 0; I < Proto->getParams().size(); ++I) { + const ParamDecl &P = Proto->getParams()[I]; + const TypeInfo *Info = Types.resolve(P.TypeName); + if (!Info) { + reportError("Unknown type %s", P.TypeName.c_str()); + return false; + } + if (!Info->isC() || Info->isVoid()) { + reportError("EXTERN parameter %zu of %s must be a non-void C type (got %s)", + I + 1, Name.c_str(), P.TypeName.c_str()); + return false; + } + ArgTypes.push_back(Info->LLVMType); + AttrBuilder B(Context); + if (Attribute::AttrKind K = Extension(Info); K != Attribute::None) B.addAttribute(K); + ParamAttrs.push_back(AttributeSet::get(Context, B)); + ParamSig PS; + PS.TypeName = P.TypeName; + Sig.Params.push_back(std::move(PS)); + } + Type *RetType = Type::getVoidTy(Context); + if (!sameIdentifier(Proto->getReturnType(), "VOID")) { + const TypeInfo *Info = Types.resolve(Proto->getReturnType()); + if (!Info) { + reportError("Unknown type %s", Proto->getReturnType().c_str()); + return false; + } + if (!Info->isC() || Info->isVoid()) { + reportError("EXTERN_FUNCTION %s must return a non-void C type (got %s)", + Name.c_str(), Proto->getReturnType().c_str()); + return false; + } + RetType = Info->LLVMType; + if (Attribute::AttrKind K = Extension(Info); K != Attribute::None) RetAttrs.addAttribute(K); + } + + auto &Overloads = Signatures[Sig.Name]; + if (!Overloads.empty()) { + if (Overloads.front().OwnerUnit != CurrentUnit) + reportError("Duplicate definition %s in %s and %s", Name.c_str(), + UnitPaths[Overloads.front().OwnerUnit].c_str(), UnitPaths[CurrentUnit].c_str()); + else reportError("Function %s cannot be redefined", Name.c_str()); + return false; + } + Sig.ExternType = FunctionType::get(RetType, ArgTypes, Proto->isVariadic()); + Sig.ExternAttrs = AttributeList::get(Context, AttributeSet(), + AttributeSet::get(Context, RetAttrs), ParamAttrs); + Module.getOrInsertFunction(Name, Sig.ExternType); + PrototypeLinks.emplace(Proto, Name); + Overloads.push_back(std::move(Sig)); + return true; +} + Function *FunctionGen::emitFunctionDef(FunctionDefAST *FuncAST, const std::function &StmtEmitter, unsigned ScopeID) { PrototypeAST *Proto = FuncAST->getProto(); @@ -309,6 +399,15 @@ llvm::Value *FunctionGen::emitCall(const std::string &Callee, const std::vector< return GenerateCall(Module, Exceptions, Callee, Args, HadError, DiagnosticLine); } +llvm::Value *FunctionGen::emitCall(const FuncSig &Sig, const std::vector &Args) { + if (!Sig.IsExternal) return emitCall(Sig.LinkName, Args); + FunctionCallee Callee(Sig.ExternType, Module.getNamedValue(Sig.LinkName)); + Value *Call = Sig.ExternType->getReturnType()->isVoidTy() + ? Exceptions.emitCall(Callee, Args) : Exceptions.emitCall(Callee, Args, "calltmp"); + cast(Call)->setAttributes(Sig.ExternAttrs); + return Call; +} + void FunctionGen::emitReturn(ReturnStmtAST *Ret, llvm::Value *RetVal) { Instruction *CatchCleanup = Exceptions.emitReturnCleanup(); Instruction *RetInst = RetVal ? Builder.CreateRet(RetVal) : Builder.CreateRetVoid(); diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 022c550..99a269d 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -15,6 +15,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { if (dynamic_cast(Stmt)) return; if (dynamic_cast(Stmt)) return; + if (dynamic_cast(Stmt) || dynamic_cast(Stmt)) return; if (auto *Class = dynamic_cast(Stmt)) { for (const auto &Method : Class->getMethods()) { -- cgit v1.3.1-4-g156e