From 699e18b65b07af73ccd14b28f83572b1843c7d37 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Wed, 14 Oct 2026 03:29:04 +0000 Subject: feat(types): add native C scalar types and imported aliases Co-Authored-By: Claude Fable 5.1 --- lib/CodeGen/ArrayHandler.cc | 2 +- lib/CodeGen/CodeGen.cc | 24 +++-- lib/CodeGen/Designator.cc | 27 +++++- lib/CodeGen/FunctionGen.cc | 6 ++ lib/CodeGen/TypeSystem.cc | 210 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 257 insertions(+), 12 deletions(-) (limited to 'lib/CodeGen') diff --git a/lib/CodeGen/ArrayHandler.cc b/lib/CodeGen/ArrayHandler.cc index 8b37b8f..bd68473 100644 --- a/lib/CodeGen/ArrayHandler.cc +++ b/lib/CodeGen/ArrayHandler.cc @@ -321,7 +321,7 @@ bool ArrayHandler::emitArrayArgument(ExprAST *ArgExpr, Callee.c_str(), ElemTypeName.c_str()); return false; } - if (Meta->ElementTypeName != ElemTypeName) { + if (!CG.resolveType(Meta->ElementTypeName)->sameType(CG.resolveType(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()); diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index f69fc03..8c01972 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -262,6 +262,7 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) const { const TypeInfo *L = getExprTypeInfo(Bin->getLHS()); const TypeInfo *R = getExprTypeInfo(Bin->getRHS()); + if ((L && L->isC()) || (R && R->isC())) return nullptr; // Enum arithmetic: E +/- INTEGER (enum on the left) yields the enum; // every other user-kind combination has no type (emission rejects it). if ((L && L->isUserKind()) || (R && R->isUserKind())) { @@ -387,6 +388,8 @@ Value *CodeGen::coerceValueToType(Value *Val, const TypeInfo *TargetInfo) { case TypeKind::Enum: case TypeKind::Record: case TypeKind::Pointer: + case TypeKind::CScalar: + case TypeKind::CPointer: reportError("Internal: a %s value reached representation coercion without a name-level check", TargetInfo->Name.c_str()); return nullptr; @@ -507,6 +510,11 @@ void CodeGen::emitOutputValue(Value *Val, const TypeInfo *Info, bool AppendNewli reportError("Cannot OUTPUT a value of pointer type %s", Info->Name.c_str()); return; + case TypeKind::CScalar: + case TypeKind::CPointer: + reportError("OUTPUT is not supported for type %s", Info->Name.c_str()); + return; + case TypeKind::Void: reportError("Cannot OUTPUT a VOID value"); return; @@ -644,6 +652,10 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { // '&' path: their admissible operations are decided by name here. const TypeInfo *LT = getExprTypeInfo(Bin->getLHS()); const TypeInfo *RT = getExprTypeInfo(Bin->getRHS()); + if ((LT && LT->isC()) || (RT && RT->isC())) { + reportError("C operators require explicit support for their operand types"); + return nullptr; + } if ((LT && LT->isUserKind()) || (RT && RT->isUserKind())) { return emitUserKindBinaryOp(Bin, LT, RT); } @@ -719,7 +731,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { } if (Name == "ASC") { const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); - if (ArgType && ArgType->isUserKind()) { + if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { reportError("ASC expects a CHAR or STRING (got %s)", ArgType->Name.c_str()); return nullptr; } @@ -747,7 +759,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { } if (Name == "NUM_TO_STR") { const TypeInfo *ArgType = getExprTypeInfo(Call->getArgs()[0].get()); - if (ArgType && ArgType->isUserKind()) { + if (ArgType && (ArgType->isUserKind() || ArgType->isC())) { reportError("NUM_TO_STR expects a number (got %s)", ArgType->Name.c_str()); return nullptr; } @@ -850,7 +862,7 @@ bool CodeGen::marshalCallArgs(const std::string &Callee, return false; } // BYREF aliases the storage, so the types must match exactly. - if (Info->TypeName != TypeName) { + if (!Types->sameType(Info->TypeName, TypeName)) { reportError("BYREF argument %s to %s must be of type %s (got %s)", Var->getName().c_str(), Callee.c_str(), TypeName.c_str(), Info->TypeName.c_str()); @@ -1093,11 +1105,11 @@ void CodeGen::emitStmt(StmtAST *Stmt) { reportError("RETURN is only allowed inside a FUNCTION or PROCEDURE"); return; } - if (Sig->ReturnTypeName == "VOID" && Ret->getRetVal()) { + if (F->getReturnType()->isVoidTy() && Ret->getRetVal()) { reportError("RETURN with a value is only allowed in a FUNCTION"); return; } - if (Sig->ReturnTypeName != "VOID" && !Ret->getRetVal()) { + if (!F->getReturnType()->isVoidTy() && !Ret->getRetVal()) { reportError("RETURN in a FUNCTION requires a value"); return; } @@ -1293,7 +1305,7 @@ void CodeGen::emitStmt(StmtAST *Stmt) { // TYPE declarations are registered by compile()'s pre-pass (the parser // guarantees they are top-level); nothing is emitted here. if (dynamic_cast(Stmt) || dynamic_cast(Stmt) || - dynamic_cast(Stmt)) { + dynamic_cast(Stmt) || dynamic_cast(Stmt)) { return; } if (auto *DA = dynamic_cast(Stmt)) { diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index 9d14d23..c7d0796 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -42,7 +42,9 @@ void CodeGen::runTypePrePass(const std::vector> &Statem for (const auto &S : Statements) { bool Ok = true; - if (auto *E = dynamic_cast(S.get())) + if (auto *C = dynamic_cast(S.get())) + Ok = Types->defineCAlias(C->getName(), C->getExpression(), Err); + else if (auto *E = dynamic_cast(S.get())) Ok = Types->declareUserType(E->getName(), TypeKind::Enum, Err); else if (auto *P = dynamic_cast(S.get())) Ok = Types->declareUserType(P->getName(), TypeKind::Pointer, Err); @@ -163,6 +165,11 @@ bool CodeGen::emitLValue(ExprAST *E, LValueInfo &Out) { TypeName.c_str()); return false; } + const TypeInfo *Pointee = resolveType(PP->PointeeTypeName); + if (!Pointee || Pointee->isVoid()) { + reportError("Cannot dereference a void pointer"); + return false; + } Value *P = Builder->CreateLoad(PointerType::getUnqual(*TheContext), Addr, "deref_ptr"); RuntimeChecker->emitNullDerefCheck(P, D->getLine()); Addr = P; @@ -213,6 +220,22 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { const TypeInfo *Src = getExprTypeInfo(E); + if (Target->isC() || (Src && Src->isC())) { + if (auto *Addr = dynamic_cast(E)) { + const PointerPayload *PP = Target->asPointer(); + LValueInfo LV; + if (PP && emitLValue(Addr->getTarget(), LV)) { + if (Types->sameType(LV.TypeName, PP->PointeeTypeName)) return LV.Addr; + } + reportError("Address does not match C pointer type %s", Target->Name.c_str()); + return nullptr; + } + if (Src && Src->sameType(Target) && !Target->isVoid()) return emitExpr(E); + reportError("Cannot implicitly convert %s to %s; use an explicit cast", + Src ? Src->Name.c_str() : "expression", Target->Name.c_str()); + return nullptr; + } + if (Target->isUserKind()) { // ^x into a pointer target is structural: the pointee type name must // match the pointer type's declared pointee exactly (strict — the @@ -226,7 +249,7 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { } LValueInfo LV; if (!emitLValue(Addr->getTarget(), LV)) return nullptr; - if (LV.TypeName != PP->PointeeTypeName) { + if (!Types->sameType(LV.TypeName, PP->PointeeTypeName)) { reportError("%s is a pointer to %s and cannot point at a value of type %s", Target->Name.c_str(), PP->PointeeTypeName.c_str(), LV.TypeName.c_str()); return nullptr; diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index afa6074..480d9ba 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -117,6 +117,12 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { std::vector ArgTypes; for (const ParamDecl &P : Proto->getParams()) { + const TypeInfo *Info = Types.resolve(P.TypeName); + if (Info && Info->isVoid()) { + fprintf(stderr, "error: parameter %s cannot have void type\n", P.Name.c_str()); + HadError = true; + return nullptr; + } ParamSig PS; PS.TypeName = P.TypeName; PS.IsByRef = P.Mode == PassMode::ByRef; diff --git a/lib/CodeGen/TypeSystem.cc b/lib/CodeGen/TypeSystem.cc index f693ba1..73d4136 100644 --- a/lib/CodeGen/TypeSystem.cc +++ b/lib/CodeGen/TypeSystem.cc @@ -2,10 +2,49 @@ #include "cps/TypeAST.h" #include "llvm/IR/Constants.h" #include +#include +#include +#include +#include +#include using namespace llvm; using namespace cps; +namespace { + +std::string cTypeName(const std::string &Identity) { + return "C_TYPE(\"" + Identity + "\")"; +} + +// sizeof alone cannot distinguish padded x87 from IEEE quad or double-double. +template Type *nativeFloatType(LLVMContext &C) { + constexpr int Digits = std::numeric_limits::digits; + constexpr int Exponent = std::numeric_limits::max_exponent; + static_assert(std::numeric_limits::radix == 2, "Unsupported native C floating-point radix"); + static_assert((Digits == 24 && Exponent == 128) || + (Digits == 53 && Exponent == 1024) || + (Digits == 64 && Exponent == 16384) || + (Digits == 113 && Exponent == 16384) || + (Digits == 106 && Exponent == 1024), "Unsupported native C floating-point format"); + if constexpr (Digits == 24) return Type::getFloatTy(C); + if constexpr (Digits == 53) return Type::getDoubleTy(C); + if constexpr (Digits == 64) return Type::getX86_FP80Ty(C); + if constexpr (Digits == 113) return Type::getFP128Ty(C); + return Type::getPPC_FP128Ty(C); +} + +} // namespace + +bool TypeInfo::isC() const { return !CIdentity.empty(); } + +bool TypeInfo::sameType(const TypeInfo *Other) const { + if (!Other) return false; + if (isC() || Other->isC()) + return isC() && Other->isC() && CIdentity == Other->CIdentity; + return Name == Other->Name; +} + const RecordFieldInfo *RecordPayload::findField(const std::string &Name) const { for (const auto &F : Fields) { if (F.first == Name) return &F.second; @@ -38,7 +77,7 @@ bool TypeInfo::isChar() const { } bool TypeInfo::isVoid() const { - return Kind == TypeKind::Void; + return Kind == TypeKind::Void || (isC() && CIdentity == "void"); } bool TypeInfo::isEnum() const { @@ -50,7 +89,7 @@ bool TypeInfo::isRecord() const { } bool TypeInfo::isPointer() const { - return Kind == TypeKind::Pointer; + return Kind == TypeKind::Pointer || Kind == TypeKind::CPointer; } bool TypeInfo::isUserKind() const { @@ -69,6 +108,8 @@ bool TypeInfo::isOutputtable() const { case TypeKind::Void: case TypeKind::Record: case TypeKind::Pointer: + case TypeKind::CScalar: + case TypeKind::CPointer: return false; } return false; @@ -83,7 +124,7 @@ const EnumPayload *TypeInfo::asEnum() const { } const PointerPayload *TypeInfo::asPointer() const { - return Kind == TypeKind::Pointer ? &Pointer : nullptr; + return isPointer() ? &Pointer : nullptr; } TypeSystem::TypeSystem(LLVMContext &Ctx) : Context(Ctx) { @@ -97,6 +138,166 @@ void TypeSystem::registerBuiltins() { registerType("STRING", PointerType::getUnqual(Context), TypeKind::String); registerType("CHAR", Type::getInt8Ty(Context), TypeKind::Char); registerType("VOID", Type::getVoidTy(Context), TypeKind::Void); + + // The compiler's native C/C++ ABI determines representation, not a table + // of assumed LP64/LLP64 widths. LLVM's addressable bytes are eight bits. + static_assert(CHAR_BIT == 8, "LLVM requires eight-bit addressable bytes"); + auto Integral = [&](auto Sample, const char *Name, unsigned Rank, + const char *InputFormat, const char *OutputFormat) { + using T = decltype(Sample); + TypeInfo Info; + Info.Name = cTypeName(Name); + Info.CIdentity = Name; + Info.Kind = TypeKind::CScalar; + Info.LLVMType = Type::getIntNTy(Context, sizeof(T) * CHAR_BIT); + Info.CRank = Rank; + Info.CSigned = std::is_signed::value; + Info.CInputFormat = InputFormat; + Info.COutputFormat = OutputFormat; + Types.emplace(Info.Name, std::move(Info)); + }; + Integral(char{}, "char", 2, " %c", "%c"); + Integral(static_cast(0), "signed char", 2, "%hhd", "%hhd"); + Integral(static_cast(0), "unsigned char", 2, "%hhu", "%hhu"); + Integral(short{}, "short", 3, "%hd", "%hd"); + Integral(static_cast(0), "unsigned short", 3, "%hu", "%hu"); + Integral(int{}, "int", 4, "%d", "%d"); + Integral(static_cast(0), "unsigned int", 4, "%u", "%u"); + Integral(long{}, "long", 5, "%ld", "%ld"); + Integral(static_cast(0), "unsigned long", 5, "%lu", "%lu"); + Integral(static_cast(0), "long long", 6, "%lld", "%lld"); + Integral(static_cast(0), "unsigned long long", 6, "%llu", "%llu"); + + auto Floating = [&](auto Sample, const char *Name, unsigned Rank, const char *Format) { + TypeInfo Info; + Info.Name = cTypeName(Name); + Info.CIdentity = Name; + Info.Kind = TypeKind::CScalar; + Info.LLVMType = nativeFloatType(Context); + Info.CRank = Rank; + Info.CInputFormat = Format; + Info.COutputFormat = Rank == 9 ? "%Lf" : "%f"; + Types.emplace(Info.Name, std::move(Info)); + }; + Floating(float{}, "float", 7, "%f"); + Floating(double{}, "double", 8, "%lf"); + Floating(static_cast(0), "long double", 9, "%Lf"); + + TypeInfo Bool; + Bool.Name = cTypeName("_Bool"); + Bool.CIdentity = "_Bool"; + Bool.Kind = TypeKind::CScalar; + Bool.LLVMType = Type::getInt1Ty(Context); + Bool.CRank = 1; + Types.emplace(Bool.Name, std::move(Bool)); + TypeInfo Void; + Void.Name = cTypeName("void"); + Void.CIdentity = "void"; + Void.Kind = TypeKind::CScalar; + Void.LLVMType = Type::getVoidTy(Context); + Types.emplace(Void.Name, std::move(Void)); + + std::string Err; + defineCAlias("C_INT", "int", Err); + defineCAlias("C_CHAR", "char", Err); + defineCAlias("C_STRING", "char*", Err); + defineCAlias("C_DOUBLE", "double", Err); +} + +bool TypeSystem::sameType(const std::string &Left, const std::string &Right) const { + const TypeInfo *L = resolve(Left); + return L && L->sameType(resolve(Right)); +} + +const TypeInfo *TypeSystem::importCType(const std::string &Expression, std::string &Err) { + // Whitespace is immaterial around stars; C's multiword base type is + // validated separately. Stars must all precede or all follow the base. + std::istringstream Input(Expression); + std::string Word, Text; + while (Input >> Word) { + if (!Text.empty()) Text += ' '; + Text += Word; + } + unsigned Depth = 0; + bool Prefix = !Text.empty() && Text.front() == '*'; + if (Prefix) { + while (!Text.empty() && (Text.front() == '*' || Text.front() == ' ')) { + if (Text.front() == '*') ++Depth; + Text.erase(Text.begin()); + } + } else { + while (!Text.empty() && (Text.back() == '*' || Text.back() == ' ')) { + if (Text.back() == '*') ++Depth; + Text.pop_back(); + } + } + std::istringstream BaseWords(Text); + std::map Counts; + bool Valid = !Text.empty(); + while (BaseWords >> Word) { + if (Word != "signed" && Word != "unsigned" && Word != "short" && + Word != "long" && Word != "int" && Word != "char" && + Word != "float" && Word != "double" && Word != "_Bool" && Word != "void") + Valid = false; + ++Counts[Word]; + } + for (const auto &Entry : Counts) + if (Entry.second > (Entry.first == "long" ? 2u : 1u)) Valid = false; + auto Has = [&](const char *Name) { return Counts.count(Name) != 0; }; + if (Has("signed") && Has("unsigned")) Valid = false; + std::string Base; + if (Has("void") || Has("_Bool") || Has("float")) { + if (Counts.size() != 1) Valid = false; + Base = Has("void") ? "void" : Has("_Bool") ? "_Bool" : "float"; + } else if (Has("double")) { + if (Counts.size() != (Has("long") ? 2u : 1u) || (Has("long") && Counts["long"] > 1)) + Valid = false; + Base = Has("long") ? "long double" : "double"; + } else if (Has("char")) { + if (Has("int") || Has("short") || Has("long")) Valid = false; + Base = Has("unsigned") ? "unsigned char" : Has("signed") ? "signed char" : "char"; + } else { + if (Has("short") && Has("long")) Valid = false; + Base = Has("short") ? "short" : Has("long") ? + (Counts["long"] == 2 ? "long long" : "long") : "int"; + if (Has("unsigned")) Base = "unsigned " + Base; + } + if (!Valid) { + Err = "Invalid or unsupported C type expression '" + Expression + "'"; + return nullptr; + } + const TypeInfo *Info = resolve(cTypeName(Base)); + for (unsigned I = 0; I < Depth; ++I) { + std::string Identity = Info->CIdentity + '*'; + const TypeInfo *Existing = resolve(cTypeName(Identity)); + if (Existing) { + Info = Existing; + continue; + } + TypeInfo Pointer; + Pointer.Name = cTypeName(Identity); + Pointer.CIdentity = Identity; + Pointer.Kind = TypeKind::CPointer; + Pointer.LLVMType = PointerType::getUnqual(Context); + Pointer.Pointer.PointeeTypeName = Info->Name; + auto Result = Types.emplace(Pointer.Name, std::move(Pointer)); + Info = &Result.first->second; + } + return Info; +} + +bool TypeSystem::defineCAlias(const std::string &Name, const std::string &Expression, + std::string &Err) { + if (Types.count(Name) || EnumConstants.count(Name)) { + Err = "Type name '" + Name + "' is already defined or reserved"; + return false; + } + const TypeInfo *C = importCType(Expression, Err); + if (!C) return false; + TypeInfo Alias = *C; + Alias.Name = Name; + Types.emplace(Name, std::move(Alias)); + return true; } const TypeInfo *TypeSystem::resolve(const std::string &TypeName) const { @@ -297,6 +498,9 @@ Constant *TypeSystem::getZeroValue(const std::string &TypeName) const { return Constant::getNullValue(Info->LLVMType); // zeroinitializer case TypeKind::Pointer: return ConstantPointerNull::get(cast(Info->LLVMType)); + case TypeKind::CScalar: + case TypeKind::CPointer: + return Constant::getNullValue(Info->LLVMType); } return Constant::getNullValue(Info->LLVMType); -- cgit v1.3.1-4-g156e