aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-17 15:06:44 +0000
committer_ov4 <ov4@ov4.org>2026-10-17 15:06:44 +0000
commitdc33cd2faf6fa52486c02e86038c8f8c07c0fe27 (patch)
treed474619d75743399e7a324c8ae5ba04056333e0b
parenta29cb23c5517fd4f8f5524fdf38b67ff5fe4a902 (diff)
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 <noreply@anthropic.com>
-rw-r--r--include/cps/CodeGen.h1
-rw-r--r--include/cps/FunctionGen.h8
-rw-r--r--lib/CodeGen/CodeGen.cc30
-rw-r--r--lib/CodeGen/Designator.cc2
-rw-r--r--lib/CodeGen/ExpressionGen.cc2
-rw-r--r--lib/CodeGen/FunctionGen.cc101
-rw-r--r--lib/CodeGen/StatementGen.cc1
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<std::unique_ptr<ExprAST>> &ArgExprs, int Line);
const FuncSig *resolveCall(const std::string &Name,
const std::vector<std::unique_ptr<ExprAST>> &Args);
+ llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position);
bool marshalCallArgs(const FuncSig &Signature,
const std::vector<std::unique_ptr<ExprAST>> &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<ParamSig> 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<void(StmtAST*)> &StmtEmitter, unsigned ScopeID);
llvm::Value *emitCall(const std::string &Callee, const std::vector<llvm::Value*> &Args);
+ llvm::Value *emitCall(const FuncSig &Sig, const std::vector<llvm::Value*> &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<SourceUnit> &Sources) {
!sameIdentifier(F->getProto()->getReturnType(), "INTEGER"))
reportError("MAIN must be a FUNCTION with no parameters returning INTEGER");
}
+ } else if (auto *X = dynamic_cast<ExternDeclAST*>(S.get())) {
+ Reserve(X->getProto()->getName(), true);
+ } else if (dynamic_cast<IncludeObjStmtAST*>(S.get())) {
} else if (auto *E = dynamic_cast<EnumTypeDeclAST*>(S.get())) {
TypeName = E->getName();
for (const auto &V : E->getValueNames()) Reserve(V);
@@ -285,6 +288,8 @@ void CodeGen::compile(const std::vector<SourceUnit> &Sources) {
SaveAndRestore<int> Location(DiagnosticLine, Stmt->getLine());
if (auto *FuncDef = dynamic_cast<FunctionDefAST*>(Stmt.get()))
FuncGen->emitPrototype(FuncDef->getProto());
+ if (auto *Extern = dynamic_cast<ExternDeclAST*>(Stmt.get()))
+ FuncGen->emitExternPrototype(Extern->getProto());
if (auto *Class = dynamic_cast<ClassTypeDeclAST*>(Stmt.get())) {
for (const auto &Method : Class->getMethods()) {
SaveAndRestore<int> MethodLocation(DiagnosticLine, Method->getLine());
@@ -365,12 +370,18 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature,
SaveAndRestore<int> 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<std::unique_ptr<ExprAST>> &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<Value*> 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<Value*> 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<Type *> ArgTypes;
+ AttrBuilder RetAttrs(Context);
+ std::vector<AttributeSet> 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<void(StmtAST*)> &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<llvm::Value*> &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<CallBase>(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<IncludeStmtAST*>(Stmt)) return;
if (dynamic_cast<GlobalBlockAST*>(Stmt)) return;
+ if (dynamic_cast<IncludeObjStmtAST*>(Stmt) || dynamic_cast<ExternDeclAST*>(Stmt)) return;
if (auto *Class = dynamic_cast<ClassTypeDeclAST*>(Stmt)) {
for (const auto &Method : Class->getMethods()) {