From 200df02e55cb6dc869728fb7ec8296adef98a68f Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Sat, 9 May 2026 02:09:52 +0200 Subject: [PATCH] support functions on struct --- src/ast.cpp | 38 +++++++++++- src/ast.h | 12 +++- src/drop_registry.cpp | 54 ++++++++++++----- src/jam_llvm.cpp | 132 +++++++++++++++--------------------------- src/jam_llvm.h | 95 +++++------------------------- src/main.cpp | 51 ++++++++++++++++ src/parser.cpp | 20 ++++++- 7 files changed, 216 insertions(+), 186 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 5943473..8b7b847 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -29,6 +29,13 @@ size_t CurrentLoopBodyScopeIdx = SIZE_MAX; // codegenAddressOf which is defined further down in the file. static JamValueRef codegenAddressOf(JamCodegenContext &ctx, const AstNode &n); +// Forward declaration so the struct-method dispatch in codegenCall can +// translate a qualified source-level name (`File.drop`) into the LLVM +// symbol (`__drop_File`). The implementation lives near declarePrototype. +static std::string mangledFunctionName(const FunctionAST &fn, + const TypePool &types, + const StringPool &strings); + // --------------------------------------------------------------------------- // Local helpers // --------------------------------------------------------------------------- @@ -233,6 +240,7 @@ static JamValueRef genPrintCall(JamCodegenContext &ctx, JamTypeRef printfType = JamLLVMFunctionType( printfRetType, printfParamTypes, 1, true); printfFunc = JamLLVMAddFunction(ctx.getModule(), "printf", printfType); + JamLLVMApplyDefaultFnAttrs(printfFunc, /*isExtern=*/true); } JamFunctionRef putsFunc = JamLLVMGetFunction(ctx.getModule(), "puts"); @@ -243,6 +251,7 @@ static JamValueRef genPrintCall(JamCodegenContext &ctx, JamTypeRef putsType = JamLLVMFunctionType( putsRetType, putsParamTypes, 1, false); putsFunc = JamLLVMAddFunction(ctx.getModule(), "puts", putsType); + JamLLVMApplyDefaultFnAttrs(putsFunc, /*isExtern=*/true); } if (callee == "std.fmt.println" && argCount == 1) { @@ -287,6 +296,7 @@ static JamValueRef genSleepCall(JamCodegenContext &ctx, const uint32_t *args, usleepParamTypes, 1, false); usleepFunc = JamLLVMAddFunction(ctx.getModule(), "usleep", usleepType); + JamLLVMApplyDefaultFnAttrs(usleepFunc, /*isExtern=*/true); } JamValueRef msArg = codegenNode(ctx, args[0]); @@ -367,6 +377,7 @@ static JamValueRef genAssertCall(JamCodegenContext &ctx, const uint32_t *args, JamTypeRef printfType = JamLLVMFunctionType( ctx.getInt32Type(), printfParamTypes, 1, true); printfFunc = JamLLVMAddFunction(ctx.getModule(), "printf", printfType); + JamLLVMApplyDefaultFnAttrs(printfFunc, /*isExtern=*/true); } JamFunctionRef exitFunc = JamLLVMGetFunction(ctx.getModule(), "exit"); if (!exitFunc) { @@ -374,6 +385,7 @@ static JamValueRef genAssertCall(JamCodegenContext &ctx, const uint32_t *args, JamTypeRef exitType = JamLLVMFunctionType( ctx.getVoidType(), exitParamTypes, 1, false); exitFunc = JamLLVMAddFunction(ctx.getModule(), "exit", exitType); + JamLLVMApplyDefaultFnAttrs(exitFunc, /*isExtern=*/true); } JamValueRef fmtStr = JamLLVMBuildGlobalStringPtr( @@ -667,8 +679,31 @@ static JamValueRef codegenCall(JamCodegenContext &ctx, const AstNode &n) { } } + // Struct-method qualified call: `Struct.method(args...)`. Methods are + // registered in the function table under their qualified source-level + // name; the LLVM symbol is whatever mangledFunctionName produces + // (drop methods share the existing `__drop_T` mangling). The rest of + // this function uses `callee` (source-level) for FunctionAST lookups + // and `llvmName` for the LLVM symbol — they only differ for methods. + std::string llvmName = callee; + { + size_t dot = callee.find('.'); + if (dot != std::string::npos && + callee.find('.', dot + 1) == std::string::npos) { + std::string typeName = callee.substr(0, dot); + if (ctx.getStruct(typeName)) { + const FunctionAST *methodAST = ctx.getFunctionAST(callee); + if (methodAST) { + llvmName = mangledFunctionName( + *methodAST, ctx.getTypePool(), + ctx.getStringPool()); + } + } + } + } + JamFunctionRef CalleeF = - JamLLVMGetFunction(ctx.getModule(), callee.c_str()); + JamLLVMGetFunction(ctx.getModule(), llvmName.c_str()); if (!CalleeF) { throw std::runtime_error("Unknown function referenced: " + callee); } @@ -2354,6 +2389,7 @@ JamFunctionRef FunctionAST::declarePrototype(JamCodegenContext &ctx) { JamFunctionRef F = JamLLVMAddFunction(ctx.getModule(), funcName.c_str(), FT); + JamLLVMApplyDefaultFnAttrs(F, isExtern); // Apply sret attributes to the leading parameter when applicable. if (rabi.kind == jam::abi::ReturnABI::Kind::Indirect) { diff --git a/src/ast.h b/src/ast.h index e2c8b99..9d4d0bd 100644 --- a/src/ast.h +++ b/src/ast.h @@ -81,14 +81,22 @@ class FunctionAST { }; // Top-level struct declaration: const Vec3 = struct { x: f32, y: f32 }; +// +// Methods declared inside the struct body — `fn name(self: ..., ...) ...` +// — live in `Methods`. The method's `FunctionAST::Name` stays as the user +// wrote it (e.g. `"drop"`); qualification (`Vec3.drop`) is applied at +// registration time in module dispatch. See docs/STRUCT_METHODS.md. class StructDeclAST { public: std::string Name; std::vector> Fields; // (name, type) + std::vector> Methods; StructDeclAST(std::string Name, - std::vector> Fields) - : Name(std::move(Name)), Fields(std::move(Fields)) {} + std::vector> Fields, + std::vector> Methods = {}) + : Name(std::move(Name)), Fields(std::move(Fields)), + Methods(std::move(Methods)) {} }; // One variant of an enum declaration. Unit variants (no payload) have diff --git a/src/drop_registry.cpp b/src/drop_registry.cpp index 2582925..acda21c 100644 --- a/src/drop_registry.cpp +++ b/src/drop_registry.cpp @@ -12,27 +12,49 @@ namespace jam { namespace drops { +// Inspect a candidate function and, if it has the drop-fn shape +// (`fn drop(self: mut )`), add it to the registry under the +// struct's name. Used by both the top-level-function scan and the +// struct-method scan below. +static void considerDropCandidate(const FunctionAST *fn, + const TypePool &types, + const StringPool &strings, + DropRegistry ®istry) { + if (fn->Name != "drop") return; + if (fn->Args.size() != 1) return; + const Param &p = fn->Args[0]; + if (p.Name != "self") return; + if (p.Mode != ParamMode::Mut) return; + + // Resolve the parameter's TypeIdx to a struct name. Both Struct and + // Named TypeKinds carry the struct's name in their `a` slot as a + // StringIdx; either form means "drop fn for that struct". + const TypeKey &key = types.get(p.Type); + if (key.kind != TypeKind::Struct && key.kind != TypeKind::Named) { + return; + } + StringIdx nameIdx = static_cast(key.a); + if (nameIdx == kNoString) return; + const std::string &structName = strings.get(nameIdx); + registry[structName] = fn; +} + DropRegistry buildDropRegistry(const ModuleAST &module, const TypePool &types, const StringPool &strings) { DropRegistry registry; + // Top-level `fn drop(self: mut T)` declarations. for (const auto &fn : module.Functions) { - if (fn->Name != "drop") continue; - if (fn->Args.size() != 1) continue; - const Param &p = fn->Args[0]; - if (p.Name != "self") continue; - if (p.Mode != ParamMode::Mut) continue; - - // Resolve the parameter's TypeIdx to a struct name. Both Struct - // and Named TypeKinds carry the struct's name in their `a` slot - // as a StringIdx; either form means "drop fn for that struct". - const TypeKey &key = types.get(p.Type); - if (key.kind != TypeKind::Struct && key.kind != TypeKind::Named) { - continue; + considerDropCandidate(fn.get(), types, strings, registry); + } + // Methods declared inside struct bodies. The validation in main.cpp + // already enforces that the self-param's type matches the enclosing + // struct, but we still re-derive the struct name from the param here + // so the registry stays self-consistent and the in-struct form is a + // pure synonym of the free-function form. + for (const auto &s : module.Structs) { + for (const auto &m : s->Methods) { + considerDropCandidate(m.get(), types, strings, registry); } - StringIdx nameIdx = static_cast(key.a); - if (nameIdx == kNoString) continue; - const std::string &structName = strings.get(nameIdx); - registry[structName] = fn.get(); } return registry; } diff --git a/src/jam_llvm.cpp b/src/jam_llvm.cpp index 119e91f..a3025d4 100644 --- a/src/jam_llvm.cpp +++ b/src/jam_llvm.cpp @@ -5,18 +5,6 @@ * Licensed under the Apache License, Version 2.0 with LLVM Exceptions. */ -/* - * Jam LLVM Wrapper Implementation - * - * This file contains all LLVM C++ API interaction, providing a C interface - * to the rest of the Jam compiler. This approach: - * 1. Reduces compile times (only this file needs LLVM headers) - * 2. Isolates LLVM version differences - * 3. Enables potential future self-hosting - * - * Inspired by Zig's zig_llvm.cpp approach. - */ - #include "jam_llvm.h" #include "llvm/IR/Constants.h" @@ -37,10 +25,6 @@ #include -// ============================================================================ -// Helper macros for type casting -// ============================================================================ - #define WRAP_CONTEXT(ctx) reinterpret_cast(ctx) #define UNWRAP_CONTEXT(ctx) reinterpret_cast(ctx) @@ -65,10 +49,6 @@ #define WRAP_TARGET_MACHINE(tm) reinterpret_cast(tm) #define UNWRAP_TARGET_MACHINE(tm) reinterpret_cast(tm) -// ============================================================================ -// Initialization -// ============================================================================ - void JamLLVMInitializeNativeTarget(void) { llvm::InitializeNativeTarget(); } void JamLLVMInitializeNativeAsmPrinter(void) { @@ -87,20 +67,22 @@ void JamLLVMInitializeAllTargets(void) { llvm::InitializeAllAsmPrinters(); } -// ============================================================================ -// Context -// ============================================================================ - JamContextRef JamLLVMCreateContext(void) { - return WRAP_CONTEXT(new llvm::LLVMContext()); + auto *ctx = new llvm::LLVMContext(); + // Drop SSA value names at construction time. Clang and Zig both emit + // IR with auto-numbered temporaries (`%0`, `%1`, …) instead of the + // source-named values our codegen passes through. Names cost LLVM + // memory (string storage on every Value) and don't affect codegen at + // all — they only show up in printed IR. Discarding them here makes + // `--emit-ir` output match what production compilers print without + // having to plumb empty strings through every CreateAlloca/CreateGEP + // call site. + ctx->setDiscardValueNames(true); + return WRAP_CONTEXT(ctx); } void JamLLVMDisposeContext(JamContextRef ctx) { delete UNWRAP_CONTEXT(ctx); } -// ============================================================================ -// Module -// ============================================================================ - JamModuleRef JamLLVMCreateModule(const char *name, JamContextRef ctx) { return WRAP_MODULE(new llvm::Module(name, *UNWRAP_CONTEXT(ctx))); } @@ -129,10 +111,6 @@ char *JamLLVMPrintModuleToString(JamModuleRef mod) { void JamLLVMDisposeMessage(char *msg) { free(msg); } -// ============================================================================ -// Builder -// ============================================================================ - JamBuilderRef JamLLVMCreateBuilder(JamContextRef ctx) { return WRAP_BUILDER(new llvm::IRBuilder<>(*UNWRAP_CONTEXT(ctx))); } @@ -150,10 +128,6 @@ JamBasicBlockRef JamLLVMGetInsertBlock(JamBuilderRef builder) { return WRAP_BLOCK(UNWRAP_BUILDER(builder)->GetInsertBlock()); } -// ============================================================================ -// Types -// ============================================================================ - JamTypeRef JamLLVMInt1Type(JamContextRef ctx) { return WRAP_TYPE(llvm::Type::getInt1Ty(*UNWRAP_CONTEXT(ctx))); } @@ -262,10 +236,6 @@ unsigned JamLLVMGetIntTypeWidth(JamTypeRef type) { return UNWRAP_TYPE(type)->getIntegerBitWidth(); } -// ============================================================================ -// Constants -// ============================================================================ - JamValueRef JamLLVMConstInt(JamTypeRef type, uint64_t val, bool signExtend) { return WRAP_VALUE( llvm::ConstantInt::get(UNWRAP_TYPE(type), val, signExtend)); @@ -297,10 +267,6 @@ JamValueRef JamLLVMGetUndef(JamTypeRef type) { return WRAP_VALUE(llvm::UndefValue::get(UNWRAP_TYPE(type))); } -// ============================================================================ -// Global Variables -// ============================================================================ - JamValueRef JamLLVMAddGlobalString(JamModuleRef mod, const char *str, const char *name) { llvm::Module *module = UNWRAP_MODULE(mod); @@ -339,10 +305,6 @@ void JamLLVMSetInitializer(JamValueRef global, JamValueRef constantVal) { ->setInitializer(llvm::cast(UNWRAP_VALUE(constantVal))); } -// ============================================================================ -// Functions -// ============================================================================ - JamFunctionRef JamLLVMAddFunction(JamModuleRef mod, const char *name, JamTypeRef funcType) { return WRAP_FUNCTION(llvm::Function::Create( @@ -409,6 +371,42 @@ void JamLLVMAddRetAttrZeroExt(JamFunctionRef func) { UNWRAP_FUNCTION(func)->addRetAttr(llvm::Attribute::ZExt); } +// Lazily-cached host CPU + feature strings. Computed once on first call and +// reused for every function — querying the host on every AddFunction would +// add up across a large module. +static const std::string &cachedHostCPU() { + static const std::string s = llvm::sys::getHostCPUName().str(); + return s; +} + +static const std::string &cachedHostFeatures() { + static const std::string s = []() { + std::string out; + auto feats = llvm::sys::getHostCPUFeatures(); + for (auto &f : feats) { + if (!out.empty()) out += ','; + out += (f.second ? '+' : '-'); + out += f.first().str(); + } + return out; + }(); + return s; +} + +void JamLLVMApplyDefaultFnAttrs(JamFunctionRef func, bool isExtern) { + llvm::Function *F = UNWRAP_FUNCTION(func); + if (!isExtern) { + F->addFnAttr(llvm::Attribute::NoUnwind); + F->addFnAttr(llvm::Attribute::getWithUWTableKind( + F->getContext(), llvm::UWTableKind::Sync)); + } + F->addFnAttr("frame-pointer", "all"); + const std::string &cpu = cachedHostCPU(); + if (!cpu.empty()) F->addFnAttr("target-cpu", cpu); + const std::string &feats = cachedHostFeatures(); + if (!feats.empty()) F->addFnAttr("target-features", feats); +} + void JamLLVMAddParamAttrSret(JamFunctionRef func, unsigned argIdx, JamTypeRef pointeeType, unsigned align) { llvm::Function *F = UNWRAP_FUNCTION(func); @@ -434,10 +432,6 @@ bool JamLLVMVerifyFunction(JamFunctionRef func) { return !llvm::verifyFunction(*UNWRAP_FUNCTION(func), &llvm::errs()); } -// ============================================================================ -// Basic Blocks -// ============================================================================ - JamBasicBlockRef JamLLVMCreateBasicBlock(JamContextRef ctx, const char *name) { return WRAP_BLOCK(llvm::BasicBlock::Create(*UNWRAP_CONTEXT(ctx), name)); } @@ -457,10 +451,6 @@ JamValueRef JamLLVMGetBasicBlockTerminator(JamBasicBlockRef block) { return WRAP_VALUE(UNWRAP_BLOCK(block)->getTerminator()); } -// ============================================================================ -// Instructions - Memory -// ============================================================================ - JamValueRef JamLLVMBuildAlloca(JamBuilderRef builder, JamTypeRef type, uint64_t alignBytes, const char *name) { auto *inst = UNWRAP_BUILDER(builder)->CreateAlloca(UNWRAP_TYPE(type), @@ -514,10 +504,6 @@ JamValueRef JamLLVMBuildPtrGEP(JamBuilderRef builder, JamTypeRef elemType, name)); } -// ============================================================================ -// Instructions - Arithmetic -// ============================================================================ - JamValueRef JamLLVMBuildAdd(JamBuilderRef builder, JamValueRef lhs, JamValueRef rhs, const char *name) { return WRAP_VALUE(UNWRAP_BUILDER(builder)->CreateAdd( @@ -572,10 +558,6 @@ JamValueRef JamLLVMBuildLShr(JamBuilderRef builder, JamValueRef lhs, UNWRAP_VALUE(lhs), UNWRAP_VALUE(rhs), name)); } -// ============================================================================ -// Instructions - Comparison -// ============================================================================ - JamValueRef JamLLVMBuildICmp(JamBuilderRef builder, JamIntPredicate pred, JamValueRef lhs, JamValueRef rhs, const char *name) { @@ -619,10 +601,6 @@ JamValueRef JamLLVMBuildICmp(JamBuilderRef builder, JamIntPredicate pred, llvmPred, UNWRAP_VALUE(lhs), UNWRAP_VALUE(rhs), name)); } -// ============================================================================ -// Instructions - Control Flow -// ============================================================================ - JamValueRef JamLLVMBuildBr(JamBuilderRef builder, JamBasicBlockRef dest) { return WRAP_VALUE(UNWRAP_BUILDER(builder)->CreateBr(UNWRAP_BLOCK(dest))); } @@ -671,10 +649,6 @@ void JamLLVMAddIncoming(JamValueRef phi, JamValueRef *values, } } -// ============================================================================ -// Instructions - Conversions -// ============================================================================ - JamValueRef JamLLVMBuildBitCast(JamBuilderRef builder, JamValueRef val, JamTypeRef destType, const char *name) { return WRAP_VALUE(UNWRAP_BUILDER(builder)->CreateBitCast( @@ -706,10 +680,6 @@ JamValueRef JamLLVMBuildFPCast(JamBuilderRef builder, JamValueRef val, UNWRAP_VALUE(val), UNWRAP_TYPE(destType), name)); } -// ============================================================================ -// Instructions - Aggregates -// ============================================================================ - JamValueRef JamLLVMBuildInsertValue(JamBuilderRef builder, JamValueRef agg, JamValueRef val, unsigned index, const char *name) { @@ -723,10 +693,6 @@ JamValueRef JamLLVMBuildExtractValue(JamBuilderRef builder, JamValueRef agg, UNWRAP_VALUE(agg), index, name)); } -// ============================================================================ -// Value Utilities -// ============================================================================ - JamTypeRef JamLLVMTypeOf(JamValueRef val) { return WRAP_TYPE(UNWRAP_VALUE(val)->getType()); } @@ -736,10 +702,6 @@ JamTypeRef JamLLVMGetAllocatedType(JamValueRef alloca) { llvm::cast(UNWRAP_VALUE(alloca))->getAllocatedType()); } -// ============================================================================ -// Target & Code Generation -// ============================================================================ - char *JamLLVMGetDefaultTargetTriple(void) { return strdup(llvm::sys::getDefaultTargetTriple().c_str()); } diff --git a/src/jam_llvm.h b/src/jam_llvm.h index 74e36fc..b41bd9c 100644 --- a/src/jam_llvm.h +++ b/src/jam_llvm.h @@ -5,17 +5,6 @@ * Licensed under the Apache License, Version 2.0 with LLVM Exceptions. */ -/* - * Jam LLVM Wrapper Layer - * - * The purpose of this file is to: - * 1. Isolate all LLVM C++ API interaction to reduce compile times - * 2. Provide a C interface for potential future self-hosting - * 3. Prevent LLVM C++ headers from infecting the rest of the project - * - * Inspired by Zig's zig_llvm.h approach. - */ - #ifndef JAM_LLVM_H #define JAM_LLVM_H @@ -67,26 +56,14 @@ typedef enum { JAM_ICMP_SLE = 41, // signed less or equal } JamIntPredicate; -// ============================================================================ -// Initialization -// ============================================================================ - JAM_EXTERN_C void JamLLVMInitializeNativeTarget(void); JAM_EXTERN_C void JamLLVMInitializeNativeAsmPrinter(void); JAM_EXTERN_C void JamLLVMInitializeNativeAsmParser(void); JAM_EXTERN_C void JamLLVMInitializeAllTargets(void); -// ============================================================================ -// Context -// ============================================================================ - JAM_EXTERN_C JamContextRef JamLLVMCreateContext(void); JAM_EXTERN_C void JamLLVMDisposeContext(JamContextRef ctx); -// ============================================================================ -// Module -// ============================================================================ - JAM_EXTERN_C JamModuleRef JamLLVMCreateModule(const char *name, JamContextRef ctx); JAM_EXTERN_C void JamLLVMDisposeModule(JamModuleRef mod); @@ -98,20 +75,12 @@ JAM_EXTERN_C JamFunctionRef JamLLVMGetFunction(JamModuleRef mod, JAM_EXTERN_C char *JamLLVMPrintModuleToString(JamModuleRef mod); JAM_EXTERN_C void JamLLVMDisposeMessage(char *msg); -// ============================================================================ -// Builder -// ============================================================================ - JAM_EXTERN_C JamBuilderRef JamLLVMCreateBuilder(JamContextRef ctx); JAM_EXTERN_C void JamLLVMDisposeBuilder(JamBuilderRef builder); JAM_EXTERN_C void JamLLVMPositionBuilderAtEnd(JamBuilderRef builder, JamBasicBlockRef block); JAM_EXTERN_C JamBasicBlockRef JamLLVMGetInsertBlock(JamBuilderRef builder); -// ============================================================================ -// Types -// ============================================================================ - JAM_EXTERN_C JamTypeRef JamLLVMInt1Type(JamContextRef ctx); JAM_EXTERN_C JamTypeRef JamLLVMInt8Type(JamContextRef ctx); JAM_EXTERN_C JamTypeRef JamLLVMInt16Type(JamContextRef ctx); @@ -143,10 +112,6 @@ JAM_EXTERN_C bool JamLLVMTypeIsPointer(JamTypeRef type); JAM_EXTERN_C bool JamLLVMTypeIsArray(JamTypeRef type); JAM_EXTERN_C unsigned JamLLVMGetIntTypeWidth(JamTypeRef type); -// ============================================================================ -// Constants -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMConstInt(JamTypeRef type, uint64_t val, bool signExtend); JAM_EXTERN_C JamValueRef JamLLVMConstReal(JamTypeRef type, double val); @@ -160,10 +125,6 @@ JAM_EXTERN_C JamValueRef JamLLVMConstStringInContext(JamContextRef ctx, bool dontNullTerminate); JAM_EXTERN_C JamValueRef JamLLVMGetUndef(JamTypeRef type); -// ============================================================================ -// Global Variables -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMAddGlobalString(JamModuleRef mod, const char *str, const char *name); @@ -176,10 +137,6 @@ JAM_EXTERN_C void JamLLVMSetGlobalConstant(JamValueRef global, bool isConstant); JAM_EXTERN_C void JamLLVMSetInitializer(JamValueRef global, JamValueRef constantVal); -// ============================================================================ -// Functions -// ============================================================================ - JAM_EXTERN_C JamFunctionRef JamLLVMAddFunction(JamModuleRef mod, const char *name, JamTypeRef funcType); @@ -196,6 +153,22 @@ JAM_EXTERN_C void JamLLVMAddParamAttrZeroExt(JamFunctionRef func, unsigned argIdx); JAM_EXTERN_C void JamLLVMAddRetAttrZeroExt(JamFunctionRef func); +// Apply the default function-level attribute set every Jam-defined function +// should carry. Mirrors what clang and Zig emit so generated IR has the same +// `#0` attribute group and benefits from the same codegen hints: +// +// nounwind — Jam has no exceptions; safe on every defined fn. +// uwtable=sync — emit unwind tables for backtraces / debuggers. +// frame-pointer="all" — keep a frame pointer (required on macOS ARM64). +// target-cpu= — let LLVM use CPU-specific instructions. +// target-features= — propagate host feature set (NEON, AVX, ...). +// +// `isExtern` skips `nounwind` and `uwtable` for declarations of foreign +// functions (we don't know whether they unwind, and the unwind table for +// them is the responsibility of whatever compiled them). +JAM_EXTERN_C void JamLLVMApplyDefaultFnAttrs(JamFunctionRef func, + bool isExtern); + // P9.6: mark a function parameter as the sret (struct-return) slot. // Equivalent to LLVM `sret() align noalias`. The argument // must be `ptr`-typed; the pointee type and alignment are passed @@ -209,10 +182,6 @@ JAM_EXTERN_C void JamLLVMSetValueName(JamValueRef val, const char *name); JAM_EXTERN_C JamTypeRef JamLLVMGetReturnType(JamFunctionRef func); JAM_EXTERN_C bool JamLLVMVerifyFunction(JamFunctionRef func); -// ============================================================================ -// Basic Blocks -// ============================================================================ - JAM_EXTERN_C JamBasicBlockRef JamLLVMCreateBasicBlock(JamContextRef ctx, const char *name); JAM_EXTERN_C JamBasicBlockRef JamLLVMAppendBasicBlock(JamFunctionRef func, @@ -220,10 +189,6 @@ JAM_EXTERN_C JamBasicBlockRef JamLLVMAppendBasicBlock(JamFunctionRef func, JAM_EXTERN_C JamFunctionRef JamLLVMGetBasicBlockParent(JamBasicBlockRef block); JAM_EXTERN_C JamValueRef JamLLVMGetBasicBlockTerminator(JamBasicBlockRef block); -// ============================================================================ -// Instructions - Memory -// ============================================================================ - // Stack alloca with an explicit alignment in bytes. Pass 0 to fall back to // LLVM's data-layout-derived inference, but prefer passing the type's real // alignment (via JamCodegenContext::typeAlign). LLVM's getPrefTypeAlign @@ -262,10 +227,6 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildPtrGEP(JamBuilderRef builder, // Returns the element type of an array type (e.g. [200 x i8] -> i8). JAM_EXTERN_C JamTypeRef JamLLVMGetArrayElementType(JamTypeRef arrayType); -// ============================================================================ -// Instructions - Arithmetic -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMBuildAdd(JamBuilderRef builder, JamValueRef lhs, JamValueRef rhs, const char *name); JAM_EXTERN_C JamValueRef JamLLVMBuildSub(JamBuilderRef builder, JamValueRef lhs, @@ -287,18 +248,10 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildLShr(JamBuilderRef builder, JamValueRef lhs, JamValueRef rhs, const char *name); -// ============================================================================ -// Instructions - Comparison -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMBuildICmp(JamBuilderRef builder, JamIntPredicate pred, JamValueRef lhs, JamValueRef rhs, const char *name); -// ============================================================================ -// Instructions - Control Flow -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMBuildBr(JamBuilderRef builder, JamBasicBlockRef dest); JAM_EXTERN_C JamValueRef JamLLVMBuildCondBr(JamBuilderRef builder, @@ -318,10 +271,6 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildPhi(JamBuilderRef builder, JamTypeRef type, JAM_EXTERN_C void JamLLVMAddIncoming(JamValueRef phi, JamValueRef *values, JamBasicBlockRef *blocks, unsigned count); -// ============================================================================ -// Instructions - Conversions -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMBuildBitCast(JamBuilderRef builder, JamValueRef val, JamTypeRef destType, @@ -343,10 +292,6 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildFPCast(JamBuilderRef builder, JamTypeRef destType, const char *name); -// ============================================================================ -// Instructions - Aggregates -// ============================================================================ - JAM_EXTERN_C JamValueRef JamLLVMBuildInsertValue(JamBuilderRef builder, JamValueRef agg, JamValueRef val, @@ -357,17 +302,9 @@ JAM_EXTERN_C JamValueRef JamLLVMBuildExtractValue(JamBuilderRef builder, unsigned index, const char *name); -// ============================================================================ -// Value Utilities -// ============================================================================ - JAM_EXTERN_C JamTypeRef JamLLVMTypeOf(JamValueRef val); JAM_EXTERN_C JamTypeRef JamLLVMGetAllocatedType(JamValueRef alloca); -// ============================================================================ -// Target & Code Generation -// ============================================================================ - JAM_EXTERN_C char *JamLLVMGetDefaultTargetTriple(void); JAM_EXTERN_C char *JamLLVMGetHostCPUName(void); JAM_EXTERN_C char *JamLLVMGetHostCPUFeatures(void); diff --git a/src/main.cpp b/src/main.cpp index 0a66cd2..3eb101a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -424,6 +424,50 @@ static int compileAndRun(const std::string &filename, codegenCtx.registerFunctionAST(function->Name, function.get()); } + // Methods declared inside struct bodies (`fn name(self: ..., ...)`). + // v1 supports `drop` only; non-drop methods are accepted by the parser + // so the surface stays forward-compatible, but rejected here so we + // don't commit to method mangling yet. See docs/STRUCT_METHODS.md. + auto resolveStructName = [&](TypeIdx ty) -> std::string { + const auto &key = codegenCtx.getTypePool().get(ty); + if (key.kind != TypeKind::Struct && + key.kind != TypeKind::Named) { + return ""; + } + return codegenCtx.getStringPool().get( + static_cast(key.a)); + }; + for (auto &s : module->Structs) { + for (auto &m : s->Methods) { + if (m->Args.empty() || m->Args[0].Name != "self") { + std::cerr << filename << ": error: method `" << m->Name + << "` on struct `" << s->Name + << "` must take `self` as its first parameter\n"; + return 1; + } + std::string selfStruct = + resolveStructName(m->Args[0].Type); + if (selfStruct != s->Name) { + std::cerr << filename << ": error: method `" << m->Name + << "` on struct `" << s->Name + << "` has self type `" << selfStruct + << "`; expected `" << s->Name << "`\n"; + return 1; + } + if (m->Name != "drop") { + std::cerr << filename + << ": error: non-drop methods inside struct " + "bodies are not yet supported (saw `" + << s->Name << "." << m->Name << "`)\n"; + return 1; + } + m->declarePrototype(codegenCtx); + codegenCtx.registerFunctionAST( + s->Name + "." + m->Name, m.get()); + mainModuleEmits.push_back(m.get()); + } + } + // Pass 2a: bodies for pub functions in imported modules. for (const auto &[path, importedModule] : resolver.getLoadedModules()) { if (path == "std") continue; @@ -444,6 +488,11 @@ static int compileAndRun(const std::string &filename, { jam::init_analysis::FunctionRegistry fnRegistry; for (auto &fn : module->Functions) { fnRegistry[fn->Name] = fn.get(); } + for (auto &s : module->Structs) { + for (auto &m : s->Methods) { + fnRegistry[s->Name + "." + m->Name] = m.get(); + } + } for (const auto &kv : resolver.getLoadedModules()) { if (kv.first == "std") continue; for (auto &fn : kv.second->Functions) { @@ -485,6 +534,7 @@ static int compileAndRun(const std::string &filename, JamTypeRef mainFT = JamLLVMFunctionType(mainRetType, nullptr, 0, false); JamFunctionRef mainFunc = JamLLVMAddFunction(codegenCtx.getModule(), "main", mainFT); + JamLLVMApplyDefaultFnAttrs(mainFunc, /*isExtern=*/false); JamLLVMSetLinkage((JamValueRef)mainFunc, JAM_LINKAGE_EXTERNAL); JamLLVMSetFunctionCallConv(mainFunc, JAM_CALLCONV_C); @@ -502,6 +552,7 @@ static int compileAndRun(const std::string &filename, codegenCtx.getInt32Type(), printfParamTypes, 1, true); printfFunc = JamLLVMAddFunction(codegenCtx.getModule(), "printf", printfType); + JamLLVMApplyDefaultFnAttrs(printfFunc, /*isExtern=*/true); } for (const auto &name : testFunctionNames) { diff --git a/src/parser.cpp b/src/parser.cpp index 6f7572c..75ac075 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -901,18 +901,32 @@ std::unique_ptr Parser::parseStructDecl() { consume(TOK_OPEN_BRACE, "Expected '{' after 'struct'"); std::vector> fields; + std::vector> methods; while (!check(TOK_CLOSE_BRACE) && !isAtEnd()) { - consume(TOK_IDENTIFIER, "Expected field name"); + // Method: `fn name(self: ..., ...) ReturnType { body }`. Methods + // can appear in any order relative to fields. parseFunction + // consumes the `fn` keyword itself. + if (check(TOK_FN)) { + methods.push_back(parseFunction()); + match(TOK_COMMA); // optional trailing comma after a method + continue; + } + // Field: `name: Type`. Comma separates from the next member. + consume(TOK_IDENTIFIER, "Expected field name or 'fn'"); std::string fieldName = previous().lexeme; consume(TOK_COLON, "Expected ':' after field name"); TypeIdx fieldType = parseType(); fields.emplace_back(std::move(fieldName), fieldType); - if (!match(TOK_COMMA)) break; + if (!check(TOK_CLOSE_BRACE)) { + consume(TOK_COMMA, + "Expected ',' or '}' after struct field"); + } } consume(TOK_CLOSE_BRACE, "Expected '}' to close struct definition"); consume(TOK_SEMI, "Expected ';' after struct declaration"); - return std::make_unique(name, std::move(fields)); + return std::make_unique(name, std::move(fields), + std::move(methods)); } // Parse `const Name = enum { Variant1, Variant2(T1, T2), ... };`. -- 2.51.2