const std = @import("std"); const ast = @import("ast.zig"); const Node = ast.Node; const Span = ast.Span; const llvm = @import("llvm_api.zig"); const c = llvm.c; const types = @import("types.zig"); const Type = types.Type; const Builtins = @import("builtins.zig").Builtins; const Parser = @import("parser.zig").Parser; const errors = @import("errors.zig"); const sema = @import("sema.zig"); const comptime_mod = @import("comptime.zig"); const unescape = @import("unescape.zig"); pub const TargetConfig = struct { /// Target triple (e.g. "aarch64-apple-darwin"). Null = host default. triple: ?[*:0]const u8 = null, /// CPU name (e.g. "generic", "apple-m1"). Null = "generic". cpu: ?[*:0]const u8 = null, /// CPU features string (e.g. "+avx2"). Null = "". features: ?[*:0]const u8 = null, /// Optimization level. opt_level: OptLevel = .default, /// Library search paths (-L flags). lib_paths: []const []const u8 = &.{}, /// Output path override. output_path: ?[]const u8 = null, /// Linker command (null = "cc" on Unix, "link.exe" on Windows). linker: ?[]const u8 = null, /// Sysroot for cross-compilation (passed as --sysroot to linker). sysroot: ?[]const u8 = null, pub const OptLevel = enum { none, less, default, aggressive, pub fn toLLVM(self: OptLevel) c.LLVMCodeGenOptLevel { return switch (self) { .none => c.LLVMCodeGenLevelNone, .less => c.LLVMCodeGenLevelLess, .default => c.LLVMCodeGenLevelDefault, .aggressive => c.LLVMCodeGenLevelAggressive, }; } }; /// Check if target triple indicates aarch64/arm64 (runtime check, not comptime). pub fn isAarch64(self: TargetConfig) bool { return self.tripleHasPrefix("aarch64", "arm64"); } /// Check if target triple indicates x86_64/x86-64. pub fn isX86_64(self: TargetConfig) bool { return self.tripleHasPrefix("x86_64", "x86-64"); } /// Check if target triple indicates Windows (contains "windows" or "win32"). pub fn isWindows(self: TargetConfig) bool { return self.tripleContains("windows") or self.tripleContains("win32"); } fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool { if (self.triple) |t| { const span = std.mem.span(t); return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2); } const dt = c.LLVMGetDefaultTargetTriple(); defer c.LLVMDisposeMessage(dt); const span = std.mem.span(dt); return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2); } fn tripleContains(self: TargetConfig, needle: []const u8) bool { if (self.triple) |t| { return std.mem.indexOf(u8, std.mem.span(t), needle) != null; } const dt = c.LLVMGetDefaultTargetTriple(); defer c.LLVMDisposeMessage(dt); return std.mem.indexOf(u8, std.mem.span(dt), needle) != null; } pub fn getCpu(self: TargetConfig) [*:0]const u8 { return self.cpu orelse "generic"; } pub fn getFeatures(self: TargetConfig) [*:0]const u8 { return self.features orelse ""; } pub fn getLinker(self: TargetConfig) []const u8 { return self.linker orelse "cc"; } }; fn baseName(name: []const u8) []const u8 { return if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; } pub const CodeGen = struct { context: c.LLVMContextRef, module: c.LLVMModuleRef, builder: c.LLVMBuilderRef, allocator: std.mem.Allocator, // ORC ThreadSafeContext — wraps the LLVMContext for JIT compatibility ts_context: c.LLVMOrcThreadSafeContextRef = null, // Whether we still own the module (false after JIT takes ownership) module_owned: bool = true, // Cached target machine (created in init, reused by emitToFile) target_machine: c.LLVMTargetMachineRef = null, // Symbol table: maps variable names to their alloca pointers named_values: std.StringHashMap(NamedValue), // Unified type registry: single lookup for all named types (structs, enums, unions, aliases) type_registry: std.StringHashMap(TypeRegistryEntry), // Flags enum registry: tracks which enum names are flags flags_enum_types: std.StringHashMap(void), // Enum variant values: maps enum name → resolved i64 values per variant enum_variant_values: std.StringHashMap([]const i64), // Enum backing types: maps enum name → LLVM type for the backing integer (default i64) enum_backing_types: std.StringHashMap(c.LLVMTypeRef), // Built-in functions (printf, etc.) builtins: ?Builtins, // Current function being generated (for alloca insertion) current_function: c.LLVMValueRef, // Return type of the current function being generated current_return_type: Type = .void_type, // Scope stack: each entry records shadowed names and deferred expressions for one scope scope_stack: std.ArrayList(Scope), // Compile-time globals: maps name to global variable info for #run results comptime_globals: std.StringHashMap(ComptimeGlobal), // Top-level #run expressions for side effects only comptime_side_effects: std.ArrayList(*Node), // Generic function templates: maps name to AST for deferred monomorphization generic_templates: std.StringHashMap(ast.FnDecl), // Instantiated generic functions: maps mangled name to LLVM function generic_instances: std.StringHashMap(c.LLVMValueRef), // Active type parameter bindings during generic instantiation (null when not instantiating) type_param_bindings: ?std.StringHashMap(Type) = null, // Active value parameter bindings during generic struct instantiation value_param_bindings: ?std.StringHashMap(i64) = null, // Active comptime param AST nodes during generic function instantiation (for #insert substitution) comptime_param_nodes: ?std.StringHashMap(*Node) = null, // Generic struct templates: maps name to AST for deferred instantiation generic_struct_templates: std.StringHashMap(ast.StructDecl), // Known namespace names (for import resolution) namespaces: std.StringHashMap(void), // Functions declared with #builtin (only available when imported) builtin_functions: std.StringHashMap(void), // Function signatures: maps function name to display signature (e.g. "test :: () -> s32") fn_signatures: std.StringHashMap([]const u8), // Active namespace during body generation of imported modules current_namespace: ?[]const u8 = null, // Diagnostics list (optional, for structured error reporting) diagnostics: ?*errors.DiagnosticList = null, // Current source span (set at genExpr/genStmt/genExprAsType entry) current_span: Span = .{ .start = 0, .end = 0 }, // Loop context: break/continue target basic blocks (null when not in a loop) loop_break_bb: c.LLVMBasicBlockRef = null, loop_continue_bb: c.LLVMBasicBlockRef = null, // Sema result (optional, for type-aware comptime evaluation) sema_result: ?*const sema.SemaResult = null, // Root declarations from the AST (for VM on-demand function compilation) root_decls: []const *Node = &.{}, // Cached LLVM struct type for string slices {ptr, i32} string_struct_type: c.LLVMTypeRef = null, // Cached LLVM struct type for Any {i32 tag, i64 value} any_struct_type: c.LLVMTypeRef = null, // Dynamic type ID assignment for Any tags (named types get unique IDs starting from 7) any_type_id_map: std.StringHashMap(u64), next_any_type_id: u64 = 7, // Cache of auto-generated to_string functions for complex types // Variadic function info: maps function name to variadic metadata variadic_functions: std.StringHashMap(VariadicInfo), // Maps function name to resolved sx parameter types (for accurate type conversion at call sites) fn_param_types: std.StringHashMap([]const Type), // Enriched Any type entries: maps type name to tag + category + sx type any_type_entries: std.StringHashMap(AnyTypeEntry), // Current match arm type entries (set during category match arm body generation) current_match_tags: ?[]const u64 = null, // Functions deferred to compile after all types are registered (e.g. any_to_string) deferred_fn_bodies: std.ArrayList(DeferredFn), // Libraries to link against (from #library directives) foreign_libraries: std.ArrayList([]const u8), // Set of foreign function names (for ABI lowering at call sites) foreign_fns: std.StringHashMap(void), // Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime) global_mutable_vars: std.StringHashMap(NamedValue), // Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip) function_return_types: std.StringHashMap(Type), // Target configuration (triple, cpu, opt level, lib paths, linker) target_config: TargetConfig = .{}, // Cached primitive LLVM types (initialized once in init(), avoids repeated FFI calls) cached_i1: c.LLVMTypeRef = null, cached_i8: c.LLVMTypeRef = null, cached_i16: c.LLVMTypeRef = null, cached_i32: c.LLVMTypeRef = null, cached_i64: c.LLVMTypeRef = null, cached_f32: c.LLVMTypeRef = null, cached_f64: c.LLVMTypeRef = null, cached_ptr: c.LLVMTypeRef = null, cached_void: c.LLVMTypeRef = null, const DeferredFn = struct { fd: ast.FnDecl, name: []const u8, // qualified name (may differ from fd.name for namespaced functions) namespace: ?[]const u8 = null, }; const TypeCategory = enum { struct_cat, enum_cat, vector_cat, array_cat, slice_cat, pointer_cat, }; const AnyTypeEntry = struct { tag_id: u64, category: TypeCategory, sx_type: Type, }; const VariadicInfo = struct { fixed_param_count: u32, // number of non-variadic params element_type_name: []const u8, // element type of the variadic slice (e.g. "s32") }; // GenericTemplate and GenericStructTemplate used to be single-field wrappers; // now the hashmaps store ast.FnDecl / ast.StructDecl directly. const ComptimeGlobal = struct { global: c.LLVMValueRef, // LLVM global variable ty: Type, // sx type expr: *Node, // the inner expression to JIT-evaluate is_resolved: bool = false, // true if initializer already set (no JIT needed) }; const StructInfo = struct { field_names: []const []const u8, field_types: []const Type, field_defaults: []const ?*Node, llvm_type: c.LLVMTypeRef, display_name: ?[]const u8 = null, // pretty name for generic instances type_param_names: []const []const u8 = &.{}, // original type param names (e.g. ["T"]) type_param_types: []const Type = &.{}, // resolved types (e.g. [s32]) template_name: ?[]const u8 = null, // original template name (e.g. "List") }; const TaggedEnumInfo = struct { variant_names: []const []const u8, variant_types: []const Type, // void_type for void variants llvm_type: c.LLVMTypeRef, // layout struct or { tag, [max_payload_size x i8] } max_payload_size: u64, payload_field_index: c_uint = 1, // struct field index of the payload array }; const PromotedField = struct { struct_name: []const u8, // the anonymous struct type name field_index: usize, // field index within that struct field_type: Type, // type of the promoted field }; const UnionInfo = struct { field_names: []const []const u8, field_types: []const Type, llvm_type: c.LLVMTypeRef, // [max_size x i8] total_size: u64, promoted_fields: std.StringHashMap(PromotedField), }; const TypeRegistryEntry = union(enum) { struct_info: StructInfo, tagged_enum: TaggedEnumInfo, union_info: UnionInfo, plain_enum: []const []const u8, alias: []const u8, }; // Scope stack entry: records what a name mapped to before being shadowed const ScopeEntry = struct { name: []const u8, prev: ?NamedValue, // null = name didn't exist before this scope }; const Scope = struct { saves: std.ArrayList(ScopeEntry), defers: std.ArrayList(*Node), }; const NamedValue = struct { ptr: c.LLVMValueRef, // alloca pointer ty: Type, // sx type is_const: bool = false, }; /// Unified value lookup result — avoids sequential hash lookups at hot paths. const ValueLookup = union(enum) { local: NamedValue, comptime_global: *ComptimeGlobal, global_mutable: NamedValue, fn ty(self: ValueLookup) Type { return switch (self) { .local, .global_mutable => |nv| nv.ty, .comptime_global => |ct| ct.ty, }; } fn ptr(self: ValueLookup) c.LLVMValueRef { return switch (self) { .local, .global_mutable => |nv| nv.ptr, .comptime_global => |ct| ct.global, }; } fn asNamedValue(self: ValueLookup) ?NamedValue { return switch (self) { .local, .global_mutable => |nv| nv, .comptime_global => null, }; } }; pub fn init(allocator: std.mem.Allocator, module_name: [*:0]const u8, target_config: TargetConfig) CodeGen { // Create context via ORC ThreadSafeContext for JIT compatibility const ts_ctx = c.LLVMOrcCreateNewThreadSafeContext(); const ctx = c.LLVMOrcThreadSafeContextGetContext(ts_ctx); const module = c.LLVMModuleCreateWithNameInContext(module_name, ctx); const builder = c.LLVMCreateBuilderInContext(ctx); // Initialize LLVM targets — native-only when targeting host, all for cross-compilation if (target_config.triple == null) { llvm.initNativeTarget(); } else { llvm.initAllTargets(); } const triple_owned = target_config.triple == null; const triple = target_config.triple orelse c.LLVMGetDefaultTargetTriple(); defer if (triple_owned) c.LLVMDisposeMessage(@constCast(triple)); c.LLVMSetTarget(module, triple); var target: c.LLVMTargetRef = null; var err_msg: [*c]u8 = null; var tm: c.LLVMTargetMachineRef = null; if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) == 0) { tm = c.LLVMCreateTargetMachine( target, triple, target_config.getCpu(), target_config.getFeatures(), target_config.opt_level.toLLVM(), c.LLVMRelocPIC, c.LLVMCodeModelDefault, ); const dl = c.LLVMCreateTargetDataLayout(tm); c.LLVMSetModuleDataLayout(module, dl); c.LLVMDisposeTargetData(dl); } else { if (err_msg != null) c.LLVMDisposeMessage(err_msg); } return .{ .context = ctx, .module = module, .builder = builder, .allocator = allocator, .ts_context = ts_ctx, .target_machine = tm, .named_values = std.StringHashMap(NamedValue).init(allocator), .type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator), .flags_enum_types = std.StringHashMap(void).init(allocator), .enum_variant_values = std.StringHashMap([]const i64).init(allocator), .enum_backing_types = std.StringHashMap(c.LLVMTypeRef).init(allocator), .builtins = null, .current_function = null, .scope_stack = std.ArrayList(Scope).empty, .comptime_globals = std.StringHashMap(ComptimeGlobal).init(allocator), .comptime_side_effects = std.ArrayList(*Node).empty, .generic_templates = std.StringHashMap(ast.FnDecl).init(allocator), .generic_instances = std.StringHashMap(c.LLVMValueRef).init(allocator), .generic_struct_templates = std.StringHashMap(ast.StructDecl).init(allocator), .namespaces = std.StringHashMap(void).init(allocator), .builtin_functions = std.StringHashMap(void).init(allocator), .fn_signatures = std.StringHashMap([]const u8).init(allocator), .variadic_functions = std.StringHashMap(VariadicInfo).init(allocator), .fn_param_types = std.StringHashMap([]const Type).init(allocator), .any_type_id_map = std.StringHashMap(u64).init(allocator), .any_type_entries = std.StringHashMap(AnyTypeEntry).init(allocator), .deferred_fn_bodies = std.ArrayList(DeferredFn).empty, .foreign_libraries = std.ArrayList([]const u8).empty, .foreign_fns = std.StringHashMap(void).init(allocator), .global_mutable_vars = std.StringHashMap(NamedValue).init(allocator), .function_return_types = std.StringHashMap(Type).init(allocator), .target_config = target_config, .cached_i1 = c.LLVMInt1TypeInContext(ctx), .cached_i8 = c.LLVMInt8TypeInContext(ctx), .cached_i16 = c.LLVMInt16TypeInContext(ctx), .cached_i32 = c.LLVMInt32TypeInContext(ctx), .cached_i64 = c.LLVMInt64TypeInContext(ctx), .cached_f32 = c.LLVMFloatTypeInContext(ctx), .cached_f64 = c.LLVMDoubleTypeInContext(ctx), .cached_ptr = c.LLVMPointerTypeInContext(ctx, 0), .cached_void = c.LLVMVoidTypeInContext(ctx), }; } pub fn deinit(self: *CodeGen) void { self.named_values.deinit(); self.type_registry.deinit(); self.comptime_globals.deinit(); self.enum_backing_types.deinit(); self.generic_templates.deinit(); self.generic_instances.deinit(); self.generic_struct_templates.deinit(); self.namespaces.deinit(); self.builtin_functions.deinit(); self.fn_signatures.deinit(); self.variadic_functions.deinit(); self.any_type_id_map.deinit(); self.any_type_entries.deinit(); self.deferred_fn_bodies.deinit(self.allocator); self.foreign_libraries.deinit(self.allocator); self.foreign_fns.deinit(); c.LLVMDisposeBuilder(self.builder); if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm); if (self.module_owned) { c.LLVMDisposeModule(self.module); } if (self.ts_context) |ts_ctx| { c.LLVMOrcDisposeThreadSafeContext(ts_ctx); } else { c.LLVMContextDispose(self.context); } } fn getStructInfo(self: *CodeGen, name: []const u8) !StructInfo { return self.lookupStructInfo(name) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{name}); } fn getTaggedEnumInfo(self: *CodeGen, name: []const u8) !TaggedEnumInfo { return self.lookupTaggedEnumInfo(name) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{name}); } fn lookupType(self: *CodeGen, name: []const u8, comptime tag: std.meta.Tag(TypeRegistryEntry)) ?switch (tag) { .struct_info => StructInfo, .tagged_enum => TaggedEnumInfo, .union_info => UnionInfo, .plain_enum => []const []const u8, .alias => []const u8, } { if (self.type_registry.get(name)) |e| { if (e == tag) return @field(e, @tagName(tag)); } return null; } fn lookupStructInfo(self: *CodeGen, name: []const u8) ?StructInfo { return self.lookupType(name, .struct_info); } fn lookupTaggedEnumInfo(self: *CodeGen, name: []const u8) ?TaggedEnumInfo { return self.lookupType(name, .tagged_enum); } fn lookupUnionInfo(self: *CodeGen, name: []const u8) ?UnionInfo { return self.lookupType(name, .union_info); } fn lookupEnumVariants(self: *CodeGen, name: []const u8) ?[]const []const u8 { return self.lookupType(name, .plain_enum); } fn lookupAlias(self: *CodeGen, name: []const u8) ?[]const u8 { return self.lookupType(name, .alias); } fn isRegisteredType(self: *CodeGen, name: []const u8) bool { return self.type_registry.contains(name); } fn resolveElementType(self: *CodeGen, name: []const u8, comptime kind: []const u8) !Type { return Type.fromName(name) orelse return self.emitErrorFmt("unknown " ++ kind ++ " element type '{s}'", .{name}); } fn emitError(self: *CodeGen, msg: []const u8) error{CodeGenError} { if (self.diagnostics) |diags| diags.add(.err, msg, self.current_span); return error.CodeGenError; } fn emitErrorFmt(self: *CodeGen, comptime fmt: []const u8, args: anytype) error{CodeGenError} { if (self.diagnostics) |diags| diags.addFmt(.err, self.current_span, fmt, args); return error.CodeGenError; } fn requireBuiltins(self: *CodeGen) !Builtins { return self.builtins orelse return self.emitError("builtins not available"); } /// Unified value lookup: checks locals, comptime globals, then global mutables. fn lookupValue(self: *CodeGen, name: []const u8) ?ValueLookup { if (self.named_values.get(name)) |nv| return .{ .local = nv }; if (self.comptime_globals.getPtr(name)) |ct| return .{ .comptime_global = ct }; if (self.global_mutable_vars.get(name)) |gm| return .{ .global_mutable = gm }; return null; } /// Build an alloca in the entry block of the current function so that /// stack space is reserved once, not on every loop iteration. fn buildEntryBlockAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: [*:0]const u8) c.LLVMValueRef { const entry_bb = c.LLVMGetEntryBasicBlock(self.current_function); const first_instr = c.LLVMGetFirstInstruction(entry_bb); const tmp_builder = c.LLVMCreateBuilderInContext(self.context); defer c.LLVMDisposeBuilder(tmp_builder); if (first_instr != null) { c.LLVMPositionBuilderBefore(tmp_builder, first_instr); } else { c.LLVMPositionBuilderAtEnd(tmp_builder, entry_bb); } return c.LLVMBuildAlloca(tmp_builder, ty, name); } /// Convert a Zig slice to a null-terminated C string using a caller-provided stack buffer. /// Returns the stack-based result when it fits, or falls back to allocator.dupeZ. fn nameToCStr(self: *CodeGen, name: []const u8, buf: *[256]u8) [*:0]const u8 { if (name.len < 256) { @memcpy(buf[0..name.len], name); buf[name.len] = 0; return @ptrCast(buf[0..name.len :0]); } const duped = self.allocator.dupeZ(u8, name) catch unreachable; return duped.ptr; } fn buildNamedAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: []const u8) !c.LLVMValueRef { var buf: [256]u8 = undefined; return self.buildEntryBlockAlloca(ty, self.nameToCStr(name, &buf)); } pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef { return switch (ty) { .signed, .unsigned => |w| switch (w) { 1 => self.cached_i1.?, 8 => self.cached_i8.?, 16 => self.cached_i16.?, 32 => self.cached_i32.?, 64 => self.cached_i64.?, else => c.LLVMIntTypeInContext(self.context, w), }, .f32 => self.f32Type(), .f64 => self.f64Type(), .void_type => self.voidType(), .boolean => self.i1Type(), .string_type, .slice_type => self.getStringStructType(), // slices use same {ptr, i32} layout .enum_type => |name| self.getEnumLLVMType(name), .struct_type => |name| if (self.lookupStructInfo(name)) |info| info.llvm_type else unreachable, .union_type => |name| if (self.lookupTaggedEnumInfo(name)) |info| info.llvm_type else if (self.lookupUnionInfo(name)) |info| info.llvm_type else unreachable, .array_type => |info| { const elem_ty = Type.fromName(info.element_name) orelse unreachable; return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length); }, .vector_type => |info| { const elem_ty = Type.fromName(info.element_name) orelse unreachable; return c.LLVMVectorType(self.typeToLLVM(elem_ty), info.length); }, .pointer_type, .many_pointer_type, .function_type => self.ptrType(), .any_type => self.getAnyStructType(), .meta_type => self.ptrType(), }; } fn getEnumLLVMType(self: *CodeGen, enum_name: []const u8) c.LLVMTypeRef { if (self.enum_backing_types.get(enum_name)) |llvm_ty| return llvm_ty; return self.i64Type(); } fn getAnyStructType(self: *CodeGen) c.LLVMTypeRef { if (self.any_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ self.i64Type(), // type tag self.i64Type(), // value (fits all primitives) }; self.any_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); return self.any_struct_type.?; } /// Type tag constants for Any type (builtins: 0-6, Type: 10, named types: 7+ dynamic) const ANY_TAG_VOID: u64 = 0; const ANY_TAG_BOOL: u64 = 1; const ANY_TAG_S32: u64 = 2; const ANY_TAG_S64: u64 = 3; const ANY_TAG_F32: u64 = 4; const ANY_TAG_F64: u64 = 5; const ANY_TAG_STRING: u64 = 6; const ANY_TAG_TYPE: u64 = 10; /// Get or assign a unique type ID for a named type (struct, enum, union, vector, array). /// IDs start at 7 and are assigned dynamically per compilation. /// Also populates `any_type_entries` with category and type info. fn getAnyTypeId(self: *CodeGen, name: []const u8, sx_type: Type) !u64 { const gop = try self.any_type_id_map.getOrPut(name); if (!gop.found_existing) { gop.value_ptr.* = self.next_any_type_id; self.next_any_type_id += 1; // Skip over reserved slot 10 (ANY_TAG_TYPE) if (self.next_any_type_id == ANY_TAG_TYPE) self.next_any_type_id += 1; // Determine category from the sx type const category: TypeCategory = switch (sx_type) { .struct_type => .struct_cat, .enum_type => .enum_cat, .union_type => .enum_cat, .vector_type => .vector_cat, .array_type => .array_cat, .slice_type => .slice_cat, .pointer_type, .many_pointer_type => .pointer_cat, else => .struct_cat, // fallback }; try self.any_type_entries.put(name, .{ .tag_id = gop.value_ptr.*, .category = category, .sx_type = sx_type, }); } return gop.value_ptr.*; } /// Check if a function should have its body compilation deferred until after all types are registered. /// Functions with non-variadic Any parameters (like any_to_string) use type-based match expressions /// that need all types registered before compilation. fn shouldDeferFnBody(fd: ast.FnDecl) bool { for (fd.params) |param| { if (!param.is_variadic and param.type_expr.data == .type_expr and std.mem.eql(u8, param.type_expr.data.type_expr.name, "Any")) { return true; } } return false; } /// Pre-register a type in the Any type system so category matching (case slice:, case array:, etc.) /// works in any_to_string even before buildAnyValue is called for this type. fn preRegisterAnyType(self: *CodeGen, sx_type: Type) !void { switch (sx_type) { .struct_type => |name| { _ = try self.getAnyTypeId(name, sx_type); // Recursively register struct field types if (self.lookupStructInfo(name)) |info| { for (info.field_types) |ft| { try self.preRegisterAnyType(ft); } } }, .enum_type => |name| _ = try self.getAnyTypeId(name, sx_type), .union_type => |name| _ = try self.getAnyTypeId(name, sx_type), .vector_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), sx_type), .array_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), sx_type), .slice_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[]{s}", .{info.element_name}), sx_type), .pointer_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "*{s}", .{info.pointee_name}), sx_type), .many_pointer_type => |info| _ = try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[*]{s}", .{info.element_name}), sx_type), else => {}, } } /// Build an Any value { tag: i32, value: i64 } from a typed LLVM value. /// Small values (ints, floats, bools, enums) are stored inline in the i64. /// Complex values (strings, structs, unions) are stored via pointer (alloca + ptr-to-int). fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, in_ty: Type) !c.LLVMValueRef { const any_ty = self.getAnyStructType(); const i64_ty = self.i64Type(); const undef = self.getUndef(any_ty); // []u8 boxes as string (same repr, same Any tag) const ty: Type = if (in_ty.isSlice() and std.mem.eql(u8, in_ty.slice_type.element_name, "u8")) .string_type else in_ty; // Determine tag const tag: u64 = switch (ty) { .void_type => ANY_TAG_VOID, .boolean => ANY_TAG_BOOL, .signed => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, .unsigned => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, .f32 => ANY_TAG_F32, .f64 => ANY_TAG_F64, .string_type => ANY_TAG_STRING, .struct_type => |name| try self.getAnyTypeId(name, ty), .enum_type => |name| try self.getAnyTypeId(name, ty), .union_type => |name| try self.getAnyTypeId(name, ty), .vector_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), ty), .array_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), ty), .slice_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[]{s}", .{info.element_name}), ty), .pointer_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "*{s}", .{info.pointee_name}), ty), .many_pointer_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[*]{s}", .{info.element_name}), ty), .meta_type => ANY_TAG_TYPE, else => ANY_TAG_S32, }; const tag_val = c.LLVMConstInt(i64_ty, tag, 0); const with_tag = self.insertValue(undef, tag_val, 0, "any_tag"); // Convert value to i64 const val_as_i64 = switch (ty) { .void_type => c.LLVMConstInt(i64_ty, 0, 0), .boolean => self.zExt(val, i64_ty, "any_bool"), .signed => |w| if (w <= 32) self.sExt(val, i64_ty, "any_int") else val, .unsigned => |w| if (w <= 32) self.zExt(val, i64_ty, "any_uint") else val, .f32 => blk: { // f32 -> f64 -> bitcast to i64 const as_f64 = c.LLVMBuildFPExt(self.builder, val, self.f64Type(), "f32_to_f64"); break :blk self.bitCast(as_f64, i64_ty, "any_f32"); }, .f64 => self.bitCast(val, i64_ty, "any_f64"), .string_type => self.allocaStoreAsI64(self.getStringStructType(), val, "any_str"), .struct_type => |sname| blk: { // Struct — store to alloca, pass pointer as i64 const info = self.lookupStructInfo(sname) orelse return self.getUndef(any_ty); break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_struct"); }, .enum_type => |ename| blk: { // Enum — extend to i64 for Any storage (no-op if already i64) const enum_llvm_ty = self.getEnumLLVMType(ename); const enum_bits = c.LLVMGetIntTypeWidth(enum_llvm_ty); if (enum_bits < 64) break :blk self.zExt(val, i64_ty, "any_enum") else break :blk val; }, .union_type => |uname| blk: { // Union — store to alloca, pass pointer as i64 const info = self.lookupTaggedEnumInfo(uname) orelse return self.getUndef(any_ty); break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_union"); }, .vector_type, .array_type => self.allocaStoreAsI64(self.typeToLLVM(ty), val, "any_vec"), .slice_type => self.allocaStoreAsI64(self.getStringStructType(), val, "any_slice"), .pointer_type, .many_pointer_type => self.ptrToInt(val, "any_ptr"), .meta_type => |mt| self.allocaStoreAsI64(self.getStringStructType(), self.buildStringSlice(val, self.constInt64(mt.name.len)), "any_type"), else => self.sExt(val, i64_ty, "any_val"), }; return self.insertValue(with_tag, val_as_i64, 1, "any_value"); } fn getStringStructType(self: *CodeGen) c.LLVMTypeRef { if (self.string_struct_type) |t| return t; var field_types = [_]c.LLVMTypeRef{ self.ptrType(), // ptr self.i64Type(), // len }; self.string_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); return self.string_struct_type.?; } /// Build a fat pointer {ptr, len} struct from a type, pointer, and length value. fn buildFatPointer(self: *CodeGen, ty: c.LLVMTypeRef, ptr: c.LLVMValueRef, len: c.LLVMValueRef) c.LLVMValueRef { const undef = self.getUndef(ty); const with_ptr = self.insertValue(undef, ptr, 0, "ptr"); return self.insertValue(with_ptr, len, 1, "len"); } /// Build a string slice {ptr, len} from a raw pointer and a length value. fn buildStringSlice(self: *CodeGen, ptr: c.LLVMValueRef, len_val: c.LLVMValueRef) c.LLVMValueRef { return self.buildFatPointer(self.getStringStructType(), ptr, len_val); } // LLVM type shortcuts (cached — no FFI call) fn i1Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i1.?; } fn i8Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i8.?; } fn i32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i32.?; } fn i64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_i64.?; } fn f32Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f32.?; } fn f64Type(self: *CodeGen) c.LLVMTypeRef { return self.cached_f64.?; } fn ptrType(self: *CodeGen) c.LLVMTypeRef { return self.cached_ptr.?; } fn voidType(self: *CodeGen) c.LLVMTypeRef { return self.cached_void.?; } fn gepArrayElement(self: *CodeGen, arr_ty: c.LLVMTypeRef, arr_ptr: c.LLVMValueRef, idx: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { var indices = [_]c.LLVMValueRef{ self.constInt32(0), idx }; return c.LLVMBuildGEP2(self.builder, arr_ty, arr_ptr, &indices, 2, name); } fn gepPointerElement(self: *CodeGen, elem_ty: c.LLVMTypeRef, ptr: c.LLVMValueRef, idx: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { var indices = [_]c.LLVMValueRef{idx}; return c.LLVMBuildGEP2(self.builder, elem_ty, ptr, &indices, 1, name); } fn arrayDecayToPointer(self: *CodeGen, arr_ty: c.LLVMTypeRef, arr_ptr: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { const zero = self.constInt64(0); var indices = [_]c.LLVMValueRef{ zero, zero }; return c.LLVMBuildGEP2(self.builder, arr_ty, arr_ptr, &indices, 2, name); } fn structGEP(self: *CodeGen, struct_ty: c.LLVMTypeRef, ptr: c.LLVMValueRef, idx: c_uint, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildStructGEP2(self.builder, struct_ty, ptr, idx, name); } fn storeStructField(self: *CodeGen, struct_ty: c.LLVMTypeRef, ptr: c.LLVMValueRef, field_idx: c_uint, val: c.LLVMValueRef) void { _ = c.LLVMBuildStore(self.builder, val, self.structGEP(struct_ty, ptr, field_idx, "field")); } fn loadStructField(self: *CodeGen, struct_ty: c.LLVMTypeRef, ptr: c.LLVMValueRef, field_idx: c_uint, field_ty: c.LLVMTypeRef) c.LLVMValueRef { return c.LLVMBuildLoad2(self.builder, field_ty, self.structGEP(struct_ty, ptr, field_idx, "field"), "fieldval"); } fn storeUndef(self: *CodeGen, ty: c.LLVMTypeRef, ptr: c.LLVMValueRef) void { _ = c.LLVMBuildStore(self.builder, self.getUndef(ty), ptr); } fn storeNull(self: *CodeGen, ty: c.LLVMTypeRef, ptr: c.LLVMValueRef) void { _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(ty), ptr); } fn loadTyped(self: *CodeGen, ty: Type, ptr: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ty), ptr, name); } const SwitchBlock = struct { merge_bb: c.LLVMBasicBlockRef, default_bb: c.LLVMBasicBlockRef, sw: c.LLVMValueRef, }; fn buildSwitch(self: *CodeGen, cond: c.LLVMValueRef, case_count: c_uint, merge_name: [*c]const u8, default_name: [*c]const u8) SwitchBlock { const merge_bb = self.appendBB(merge_name); const default_bb = self.appendBB(default_name); const sw = c.LLVMBuildSwitch(self.builder, cond, default_bb, case_count); return .{ .merge_bb = merge_bb, .default_bb = default_bb, .sw = sw }; } fn loadIfPointer(self: *CodeGen, val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { if (c.LLVMGetTypeKind(c.LLVMTypeOf(val)) == c.LLVMPointerTypeKind) { return c.LLVMBuildLoad2(self.builder, ty, val, name); } return val; } fn loadFromI64Ptr(self: *CodeGen, i64_val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { const ptr = self.intToPtr(i64_val, name); return c.LLVMBuildLoad2(self.builder, ty, ptr, name); } fn resolveAlias(self: *CodeGen, name: []const u8) []const u8 { return self.lookupAlias(name) orelse name; } fn buildPhiNode(self: *CodeGen, phi_vals: *std.ArrayList(c.LLVMValueRef), phi_bbs: *std.ArrayList(c.LLVMBasicBlockRef), ty: c.LLVMTypeRef, name: [*c]const u8) !c.LLVMValueRef { const vals_slice = try phi_vals.toOwnedSlice(self.allocator); const bbs_slice = try phi_bbs.toOwnedSlice(self.allocator); const phi = c.LLVMBuildPhi(self.builder, ty, name); c.LLVMAddIncoming(phi, vals_slice.ptr, bbs_slice.ptr, @intCast(vals_slice.len)); return phi; } fn addPhiCase(self: *CodeGen, phi_vals: *std.ArrayList(c.LLVMValueRef), phi_bbs: *std.ArrayList(c.LLVMBasicBlockRef), val: c.LLVMValueRef, merge_bb: c.LLVMBasicBlockRef) !void { try phi_vals.append(self.allocator, val); try phi_bbs.append(self.allocator, self.getCurrentBlock()); self.br(merge_bb); } fn getTypeSize(self: *CodeGen, ty: c.LLVMTypeRef) u64 { return c.LLVMStoreSizeOfType(c.LLVMGetModuleDataLayout(self.module), ty); } fn appendBlock(self: *CodeGen, function: c.LLVMValueRef, name: [*c]const u8) c.LLVMBasicBlockRef { const bb = c.LLVMAppendBasicBlockInContext(self.context, function, name); self.positionAt(bb); return bb; } fn valueToBool(self: *CodeGen, val: c.LLVMValueRef) c.LLVMValueRef { if (c.LLVMTypeOf(val) == self.i1Type()) return val; return self.icmp(c.LLVMIntNE, val, c.LLVMConstInt(c.LLVMTypeOf(val), 0, 0), "tobool"); } fn constInt64(self: *CodeGen, val: u64) c.LLVMValueRef { return c.LLVMConstInt(self.i64Type(), val, 0); } fn constInt32(self: *CodeGen, val: u32) c.LLVMValueRef { return c.LLVMConstInt(self.i32Type(), val, 0); } fn extractValue(self: *CodeGen, val: c.LLVMValueRef, idx: c_uint, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildExtractValue(self.builder, val, idx, name); } fn buildGlobalString(self: *CodeGen, str: [*c]const u8, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildGlobalStringPtr(self.builder, str, name); } fn insertValue(self: *CodeGen, aggr: c.LLVMValueRef, val: c.LLVMValueRef, idx: c_uint, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildInsertValue(self.builder, aggr, val, idx, name); } fn positionAt(self: *CodeGen, bb: c.LLVMBasicBlockRef) void { c.LLVMPositionBuilderAtEnd(self.builder, bb); } fn br(self: *CodeGen, dest: c.LLVMBasicBlockRef) void { _ = c.LLVMBuildBr(self.builder, dest); } fn condBr(self: *CodeGen, cond: c.LLVMValueRef, then_bb: c.LLVMBasicBlockRef, else_bb: c.LLVMBasicBlockRef) void { _ = c.LLVMBuildCondBr(self.builder, cond, then_bb, else_bb); } fn allocaStoreAsI64(self: *CodeGen, ty: c.LLVMTypeRef, val: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { const alloca = self.buildEntryBlockAlloca(ty, name); _ = c.LLVMBuildStore(self.builder, val, alloca); return self.ptrToInt(alloca, name); } fn trunc(self: *CodeGen, val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildTrunc(self.builder, val, ty, name); } fn zExt(self: *CodeGen, val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildZExt(self.builder, val, ty, name); } fn sExt(self: *CodeGen, val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildSExt(self.builder, val, ty, name); } fn bitCast(self: *CodeGen, val: c.LLVMValueRef, ty: c.LLVMTypeRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildBitCast(self.builder, val, ty, name); } fn appendBB(self: *CodeGen, name: [*c]const u8) c.LLVMBasicBlockRef { return c.LLVMAppendBasicBlockInContext(self.context, self.current_function, name); } fn getCurrentBlock(self: *CodeGen) c.LLVMBasicBlockRef { return c.LLVMGetInsertBlock(self.builder); } fn ret(self: *CodeGen, val: c.LLVMValueRef) void { _ = c.LLVMBuildRet(self.builder, val); } fn retVoid(self: *CodeGen) void { _ = c.LLVMBuildRetVoid(self.builder); } fn intToPtr(self: *CodeGen, val: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildIntToPtr(self.builder, val, self.ptrType(), name); } fn ptrToInt(self: *CodeGen, ptr: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildPtrToInt(self.builder, ptr, self.i64Type(), name); } fn icmp(self: *CodeGen, pred: c.LLVMIntPredicate, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, name: [*c]const u8) c.LLVMValueRef { return c.LLVMBuildICmp(self.builder, pred, lhs, rhs, name); } fn getUndef(_: *CodeGen, ty: c.LLVMTypeRef) c.LLVMValueRef { return c.LLVMGetUndef(ty); } /// Extract .len or .ptr from a fat pointer value ({ptr, len} struct). fn extractFatPtrField(self: *CodeGen, val: c.LLVMValueRef, field: []const u8, type_name: []const u8) !c.LLVMValueRef { if (std.mem.eql(u8, field, "len")) { return self.extractValue(val, 1, "len"); } if (std.mem.eql(u8, field, "ptr")) { return self.extractValue(val, 0, "ptr"); } return self.emitErrorFmt("no field '{s}' on {s} (available: .len, .ptr)", .{ field, type_name }); } fn pushScope(self: *CodeGen) !void { var saves = std.ArrayList(ScopeEntry).empty; try saves.ensureTotalCapacity(self.allocator, 8); var defers = std.ArrayList(*Node).empty; try defers.ensureTotalCapacity(self.allocator, 4); try self.scope_stack.append(self.allocator, .{ .saves = saves, .defers = defers }); } fn popScope(self: *CodeGen) !void { if (self.scope_stack.items.len == 0) return; const scope = self.scope_stack.items[self.scope_stack.items.len - 1]; // 1. Execute deferred expressions in LIFO order var i: usize = scope.defers.items.len; while (i > 0) { i -= 1; _ = try self.genExpr(scope.defers.items[i]); } // 2. Restore shadowed variables in reverse order i = scope.saves.items.len; while (i > 0) { i -= 1; const entry = scope.saves.items[i]; if (entry.prev) |prev| { self.named_values.putAssumeCapacity(entry.name, prev); } else { _ = self.named_values.remove(entry.name); } } _ = self.scope_stack.pop(); } /// Emit all pending deferred expressions from all active scopes (LIFO order, /// innermost scope first). Does NOT pop the stacks — used before `return` /// so that popScope() can still clean up the data structures later. fn emitAllDefers(self: *CodeGen) !void { var i: usize = self.scope_stack.items.len; while (i > 0) { i -= 1; const defers = self.scope_stack.items[i].defers; var j: usize = defers.items.len; while (j > 0) { j -= 1; _ = try self.genExpr(defers.items[j]); } } } fn saveShadowed(self: *CodeGen, name: []const u8) !void { if (self.scope_stack.items.len == 0) return; const top = &self.scope_stack.items[self.scope_stack.items.len - 1].saves; const prev = self.named_values.get(name); try top.append(self.allocator, .{ .name = name, .prev = prev }); } fn registerVariable(self: *CodeGen, name: []const u8, ptr: c.LLVMValueRef, ty: Type) !void { try self.saveShadowed(name); try self.named_values.put(name, .{ .ptr = ptr, .ty = ty }); } pub fn generate(self: *CodeGen, root: *Node) !void { if (root.data != .root) return self.emitError("expected root node for code generation"); // Store root decls for VM on-demand function compilation self.root_decls = root.data.root.decls; // Initialize built-in function declarations (printf, etc.) self.builtins = Builtins.init(self.module, self.context); // Pass 1: Register all declarations (signatures only, no bodies) for (root.data.root.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr) { try self.builtin_functions.put(fd.name, {}); } else if (fd.body.data == .foreign_expr) { // External C function — register LLVM declaration (no body) try self.registerFnDecl(fd, fd.name); } else if (fd.type_params.len > 0) { try self.generic_templates.put(fd.name, fd); } else { try self.registerFnDecl(fd, fd.name); } try self.fn_signatures.put(fd.name, self.buildFnSignature(fd)); }, .library_decl => |ld| { try self.foreign_libraries.append(self.allocator, ld.lib_name); }, .enum_decl => |ed| { if (ed.variant_types.len > 0) { // Tagged enum with payloads try self.registerTaggedEnum(ed); } else { // Payload-less enum try self.type_registry.put(ed.name, .{ .plain_enum = ed.variant_names }); _ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name }); if (ed.is_flags) { try self.flags_enum_types.put(ed.name, {}); } // Register backing type if specified if (ed.backing_type) |bt_node| { const bt = self.resolveType(bt_node); try self.enum_backing_types.put(ed.name, self.typeToLLVM(bt)); } // Compute and store variant values const values = try self.allocator.alloc(i64, ed.variant_names.len); for (ed.variant_names, 0..) |_, i| { if (ed.variant_values.len > i and ed.variant_values[i] != null) { // Explicit value: evaluate comptime int literal const val_node = ed.variant_values[i].?; values[i] = switch (val_node.data) { .int_literal => |il| il.value, else => @as(i64, @intCast(i)), // fallback }; } else if (ed.is_flags) { // Auto power-of-2: 1, 2, 4, 8, ... values[i] = @as(i64, 1) << @intCast(i); } else { // Regular enum: sequential 0, 1, 2, ... values[i] = @intCast(i); } } try self.enum_variant_values.put(ed.name, values); } }, .struct_decl => |sd| try self.registerStructType(sd), .union_decl => |ud| try self.registerUnionType(ud), .const_decl => |cd| { if (cd.value.data == .builtin_expr) { // #builtin constant — skip codegen } else if (cd.value.data == .lambda) { try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); } else if (cd.value.data == .type_expr) { try self.type_registry.put(cd.name, .{ .alias = cd.value.data.type_expr.name }); } else if (cd.value.data == .call) { // Check if this is a generic struct or type function instantiation const callee_name = if (cd.value.data.call.callee.data == .identifier) cd.value.data.call.callee.data.identifier.name else null; if (callee_name) |cn| { if (self.generic_struct_templates.get(cn)) |tmpl| { // Generic struct instantiation: Vec3 :: Vec(3, f32); const result_ty = try self.instantiateGenericStruct(cn, tmpl, cd.value.data.call.args); if (result_ty.isStruct()) { try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type }); } } else if (self.generic_templates.get(cn)) |tmpl| { // Type-returning function: Foo :: Complex(u32); const result_ty = try self.instantiateTypeFunction(cd.name, cn, tmpl, cd.value.data.call.args); if (result_ty.isStruct()) { try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type }); } else if (result_ty.isUnion()) { try self.type_registry.put(cd.name, .{ .alias = result_ty.union_type }); } } else if (self.builtin_functions.contains(cn)) { // Builtin type function (e.g., Vector(4, f32), Array(5, s32)) if (self.resolveBuiltinType(cn, cd.value.data.call.args)) |result_ty| { const display = try result_ty.displayName(self.allocator); try self.type_registry.put(cd.name, .{ .alias = display }); } else { try self.registerTopLevelConstant(cd); } } else { try self.registerTopLevelConstant(cd); } } else { try self.registerTopLevelConstant(cd); } } else if (cd.value.data == .comptime_expr) { // Use explicit type annotation if available const ct_type_override: ?Type = if (cd.type_annotation) |te| Type.fromTypeExpr(te) else null; try self.registerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, ct_type_override); } else { // Top-level value constant (e.g., SPECIAL_VALUE :u8: 42;) try self.registerTopLevelConstant(cd); } }, .comptime_expr => |ct| { try self.comptime_side_effects.append(self.allocator, ct.expr); }, .namespace_decl => |ns| { try self.registerNamespace(ns); }, .var_decl => |vd| { try self.registerGlobalVar(vd); }, else => {}, } } // Pass 2: Generate all function bodies // Functions with Any parameters (like any_to_string) are deferred to Pass 3 // so that all types are registered before their type-match expressions are compiled. for (root.data.root.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) { // skip — no body to generate } else if (fd.type_params.len == 0) { if (shouldDeferFnBody(fd)) { try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name }); } else { try self.genFnBody(fd, fd.name); } } }, .const_decl => |cd| { if (cd.value.data == .lambda) { try self.genLambdaBody(cd.name, cd.value.data.lambda); } }, .namespace_decl => |ns| { try self.genNamespaceBodies(ns); }, else => {}, } } // Pass 3: Compile deferred function bodies (after all types are registered) for (self.deferred_fn_bodies.items) |deferred| { const saved_ns = self.current_namespace; self.current_namespace = deferred.namespace; defer self.current_namespace = saved_ns; try self.genFnBody(deferred.fd, deferred.name); } // Execute comptime side effects via bytecode VM (e.g., #run main();) for (self.comptime_side_effects.items) |expr| { _ = try self.comptimeEval(expr, .void_type); } } /// Evaluate a comptime expression using the bytecode VM. /// No LLVM state save/restore needed — the VM operates independently. fn comptimeEval(self: *CodeGen, expr: *Node, expected_type: Type) !comptime_mod.Value { _ = expected_type; // VM infers types from values; expected_type used by caller for LLVM conversion var compiler = comptime_mod.Compiler.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self); const chunk = compiler.compile(expr) catch |err| { return self.emitErrorFmt("comptime compilation failed: {s}", .{@errorName(err)}); }; var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self); return vm.execute(&chunk) catch |err| { return self.emitErrorFmt("comptime execution failed: {s}", .{@errorName(err)}); }; } /// Substitute comptime param identifiers in an AST expression with their literal nodes. /// Used before comptimeEval in #insert to resolve comptime function params. fn substituteComptimeNodes(self: *CodeGen, node: *Node) !*Node { const cpn = self.comptime_param_nodes orelse return node; // Direct identifier match if (node.data == .identifier) { if (cpn.get(node.data.identifier.name)) |replacement| { return replacement; } } // Recurse into call arguments if (node.data == .call) { var new_args = try self.allocator.alloc(*Node, node.data.call.args.len); var changed = false; for (node.data.call.args, 0..) |arg, i| { new_args[i] = try self.substituteComptimeNodes(arg); if (new_args[i] != arg) changed = true; } if (changed) { const new_node = try self.allocator.create(Node); new_node.* = .{ .span = node.span, .data = .{ .call = .{ .callee = node.data.call.callee, .args = new_args, } }, }; return new_node; } } // Recurse into binary ops if (node.data == .binary_op) { const new_lhs = try self.substituteComptimeNodes(node.data.binary_op.lhs); const new_rhs = try self.substituteComptimeNodes(node.data.binary_op.rhs); if (new_lhs != node.data.binary_op.lhs or new_rhs != node.data.binary_op.rhs) { const new_node = try self.allocator.create(Node); new_node.* = .{ .span = node.span, .data = .{ .binary_op = .{ .op = node.data.binary_op.op, .lhs = new_lhs, .rhs = new_rhs, } }, }; return new_node; } } return node; } /// Convert a comptime VM Value to an LLVM constant value. fn comptimeValueToLLVM(self: *CodeGen, value: comptime_mod.Value, ty: Type) c.LLVMValueRef { return switch (value) { .int_val => |v| c.LLVMConstInt(self.typeToLLVM(ty), @bitCast(v), 0), .float_val => |v| c.LLVMConstReal(self.f64Type(), v), .float32_val => |v| c.LLVMConstReal(self.f32Type(), @as(f64, v)), .bool_val => |v| c.LLVMConstInt(self.i1Type(), if (v) 1 else 0, 0), .string_val => |v| blk: { const z = self.allocator.dupeZ(u8, v) catch unreachable; const ptr = self.buildGlobalString(z.ptr, "comptime_str"); break :blk self.buildStringSlice(ptr, self.constInt64(@intCast(v.len))); }, .void_val => self.constInt32(0), .pointer_val => c.LLVMConstNull(self.ptrType()), .null_val => c.LLVMConstNull(self.ptrType()), .struct_val, .array_val, .type_val, .function_val => unreachable, }; } /// Lazily resolve a comptime global by evaluating its expression via bytecode VM. fn resolveComptimeGlobal(self: *CodeGen, ct: *ComptimeGlobal) !void { const result = try self.comptimeEval(ct.expr, ct.ty); const const_val = self.comptimeValueToLLVM(result, ct.ty); c.LLVMSetInitializer(ct.global, const_val); c.LLVMSetGlobalConstant(ct.global, 1); ct.is_resolved = true; } fn resolveType(self: *CodeGen, type_node: ?*Node) Type { if (type_node) |tn| { if (Type.fromTypeExpr(tn)) |t| return t; // Array type: [N]T if (tn.data == .array_type_expr) { const ate = tn.data.array_type_expr; const length: u32 = @intCast(ate.length.data.int_literal.value); const elem_type = self.resolveType(ate.element_type); const elem_name = elem_type.displayName(self.allocator) catch unreachable; return .{ .array_type = .{ .element_name = elem_name, .length = length } }; } // Slice type: []T if (tn.data == .slice_type_expr) { const ste = tn.data.slice_type_expr; const elem_type = self.resolveType(ste.element_type); const elem_name = elem_type.displayName(self.allocator) catch unreachable; return .{ .slice_type = .{ .element_name = elem_name } }; } // Pointer type: *T if (tn.data == .pointer_type_expr) { const pte = tn.data.pointer_type_expr; const pointee_type = self.resolveType(pte.pointee_type); const pointee_name = pointee_type.displayName(self.allocator) catch unreachable; return .{ .pointer_type = .{ .pointee_name = pointee_name } }; } // Many-pointer type: [*]T if (tn.data == .many_pointer_type_expr) { const mpte = tn.data.many_pointer_type_expr; const elem_type = self.resolveType(mpte.element_type); const elem_name = elem_type.displayName(self.allocator) catch unreachable; return .{ .many_pointer_type = .{ .element_name = elem_name } }; } // Function pointer type: (ParamTypes) -> ReturnType if (tn.data == .function_type_expr) { const fte = tn.data.function_type_expr; var param_types = std.ArrayList(Type).empty; for (fte.param_types) |pt| { param_types.append(self.allocator, self.resolveType(pt)) catch return .void_type; } const ret_ty = if (fte.return_type) |rt| self.resolveType(rt) else Type.void_type; const ret_ptr = self.allocator.create(Type) catch return .void_type; ret_ptr.* = ret_ty; return .{ .function_type = .{ .param_types = param_types.toOwnedSlice(self.allocator) catch return .void_type, .return_type = ret_ptr, } }; } // Parameterized type: Vector(N, T) or generic struct instantiation if (tn.data == .parameterized_type_expr) { const pte = tn.data.parameterized_type_expr; // Direct lookup (unqualified names from flat imports) if (self.builtin_functions.contains(pte.name)) { if (self.resolveBuiltinType(pte.name, pte.args)) |ty| return ty; } if (self.generic_struct_templates.get(pte.name)) |tmpl| { return self.instantiateGenericStruct(pte.name, tmpl, pte.args) catch .void_type; } // Progressive namespace resolution for dotted names (e.g. "std.Vector") if (std.mem.indexOfScalar(u8, pte.name, '.')) |dot| { const ns = pte.name[0..dot]; if (self.namespaces.contains(ns)) { // Namespace verified — look up qualified name in registries if (self.builtin_functions.contains(pte.name)) { if (self.resolveBuiltinType(pte.name, pte.args)) |ty| return ty; } if (self.generic_struct_templates.get(pte.name)) |tmpl| { return self.instantiateGenericStruct(pte.name, tmpl, pte.args) catch .void_type; } } } if (self.diagnostics) |diags| diags.addFmt(.err, tn.span, "unresolved type '{s}'", .{pte.name}); return .void_type; } // Call expression as type: Vec(3, f32) → generic struct/type function instantiation if (tn.data == .call) { const name = self.calleeToQualifiedName(tn.data.call.callee); if (name) |n| { if (self.builtin_functions.contains(n)) { if (self.resolveBuiltinType(n, tn.data.call.args)) |ty| return ty; } if (self.generic_struct_templates.get(n)) |tmpl| { return self.instantiateGenericStruct(n, tmpl, tn.data.call.args) catch .void_type; } if (self.generic_templates.get(n)) |tmpl| { return self.instantiateTypeFunction(n, n, tmpl, tn.data.call.args) catch .void_type; } } return .void_type; } // Check type parameter bindings (during generic instantiation) if (tn.data == .type_expr or tn.data == .identifier) { const name = if (tn.data == .type_expr) tn.data.type_expr.name else tn.data.identifier.name; // Try primitive type name first if (Type.fromName(name)) |t| return t; if (self.type_param_bindings) |bindings| { if (bindings.get(name)) |t| return t; } // Unified type registry lookup if (self.type_registry.get(name)) |entry| { switch (entry) { .struct_info => return .{ .struct_type = name }, .tagged_enum => return .{ .union_type = name }, .union_info => return .{ .union_type = name }, .plain_enum => return .{ .enum_type = name }, .alias => |target| { if (Type.fromName(target)) |t| return t; if (self.type_registry.get(target)) |inner| { switch (inner) { .struct_info => return .{ .struct_type = target }, .tagged_enum => return .{ .union_type = target }, .union_info => return .{ .union_type = target }, .plain_enum => return .{ .enum_type = target }, .alias => {}, } } }, } } } // Safety net: inline declarations that should have been hoisted if (tn.data == .struct_decl) { const sn = tn.data.struct_decl.name; if (self.type_registry.get(sn)) |e| { if (e == .struct_info) return .{ .struct_type = sn }; } } if (tn.data == .enum_decl) { const en = tn.data.enum_decl.name; if (self.type_registry.get(en)) |e| switch (e) { .tagged_enum => return .{ .union_type = en }, .plain_enum => return .{ .enum_type = en }, else => {}, }; } return .void_type; } return .void_type; } /// Resolve a value argument to an integer — handles int_literal and identifier referencing value_param_bindings. fn resolveValueArg(self: *CodeGen, node: *Node) i64 { if (node.data == .int_literal) return node.data.int_literal.value; if (node.data == .identifier or node.data == .type_expr) { const name = if (node.data == .identifier) node.data.identifier.name else node.data.type_expr.name; if (self.value_param_bindings) |bindings| { if (bindings.get(name)) |val| return val; } } return 0; } /// Instantiate a generic struct template with concrete arguments. /// Returns the struct_type for the instantiated struct (possibly cached). fn instantiateGenericStruct(self: *CodeGen, template_name: []const u8, tmpl: ast.StructDecl, args: []const *Node) !Type { const sd = tmpl; // Build bindings from template params + args var type_bindings = std.StringHashMap(Type).init(self.allocator); var val_bindings = std.StringHashMap(i64).init(self.allocator); for (sd.type_params, 0..) |tp, i| { if (i >= args.len) return self.emitErrorFmt("generic struct '{s}' expects {d} type arguments, got {d}", .{ template_name, sd.type_params.len, args.len }); const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; if (std.mem.eql(u8, constraint_name, "Type")) { // Type parameter: resolve arg as type const resolved = self.resolveType(args[i]); try type_bindings.put(tp.name, resolved); } else { // Value parameter: resolve arg as integer const val = self.resolveValueArg(args[i]); try val_bindings.put(tp.name, val); } } const mangled_name = try self.mangleGenericName(template_name, sd.type_params, type_bindings, val_bindings, null); // Check if already instantiated if (self.type_registry.contains(mangled_name)) { return .{ .struct_type = mangled_name }; } // Instantiate: resolve field types with bindings active const saved_type_bindings = self.type_param_bindings; const saved_value_bindings = self.value_param_bindings; self.type_param_bindings = type_bindings; self.value_param_bindings = val_bindings; defer { self.type_param_bindings = saved_type_bindings; self.value_param_bindings = saved_value_bindings; } const build = try self.buildStructFields(mangled_name, sd.field_types); const resolved_defaults = try self.allocator.dupe(?*Node, sd.field_defaults); // Build pretty display name: Vec(3,f32) var display_buf = std.ArrayList(u8).empty; try display_buf.appendSlice(self.allocator, template_name); try display_buf.append(self.allocator, '('); for (sd.type_params, 0..) |tp, i| { if (i > 0) try display_buf.appendSlice(self.allocator, ","); const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; if (std.mem.eql(u8, constraint_name, "Type")) { if (type_bindings.get(tp.name)) |ty| { const dn = ty.displayName(self.allocator) catch "?"; try display_buf.appendSlice(self.allocator, dn); } } else { if (val_bindings.get(tp.name)) |val| { var tmp: [20]u8 = undefined; const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; try display_buf.appendSlice(self.allocator, s); } } } try display_buf.append(self.allocator, ')'); const display_name = try display_buf.toOwnedSlice(self.allocator); // Collect type param names and resolved types for later extraction var tp_names = std.ArrayList([]const u8).empty; var tp_types = std.ArrayList(Type).empty; for (sd.type_params) |tp| { const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; if (std.mem.eql(u8, constraint_name, "Type")) { if (type_bindings.get(tp.name)) |ty| { try tp_names.append(self.allocator, tp.name); try tp_types.append(self.allocator, ty); } } } const si = StructInfo{ .field_names = sd.field_names, .field_types = build.field_sx_types, .field_defaults = resolved_defaults, .llvm_type = build.llvm_type, .display_name = display_name, .type_param_names = try tp_names.toOwnedSlice(self.allocator), .type_param_types = try tp_types.toOwnedSlice(self.allocator), .template_name = template_name, }; try self.type_registry.put(mangled_name, .{ .struct_info = si }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); return .{ .struct_type = mangled_name }; } /// Instantiate a type-returning function (e.g. Complex(u32)) by walking the body AST /// to find `return struct { ... }` or `return union { ... }` and registering with bindings active. fn instantiateTypeFunction(self: *CodeGen, alias_name: []const u8, template_name: []const u8, tmpl: ast.FnDecl, args: []const *Node) !Type { const fd = tmpl; // Build type bindings from params + args var type_bindings = std.StringHashMap(Type).init(self.allocator); for (fd.type_params, 0..) |tp, i| { if (i >= args.len) return self.emitErrorFmt("type function '{s}' expects {d} type arguments, got {d}", .{ template_name, fd.type_params.len, args.len }); const resolved = self.resolveType(args[i]); try type_bindings.put(tp.name, resolved); } // Activate bindings const saved_type_bindings = self.type_param_bindings; self.type_param_bindings = type_bindings; defer self.type_param_bindings = saved_type_bindings; const mangled_name = try self.mangleGenericName(template_name, fd.type_params, type_bindings, null, null); // Try struct first if (self.findStructInBody(fd.body)) |struct_decl| { if (self.type_registry.contains(mangled_name)) { return .{ .struct_type = mangled_name }; } return self.registerInstantiatedStruct(mangled_name, alias_name, struct_decl); } // Try union if (self.findUnionInBody(fd.body)) |union_decl| { if (self.type_registry.contains(mangled_name)) { return .{ .union_type = mangled_name }; } return self.registerInstantiatedTaggedEnum(mangled_name, union_decl); } return self.emitErrorFmt("type function '{s}' does not return a struct or enum", .{template_name}); } fn registerInstantiatedStruct(self: *CodeGen, mangled_name: []const u8, alias_name: []const u8, struct_decl: ast.StructDecl) !Type { const build = try self.buildStructFields(mangled_name, struct_decl.field_types); const resolved_defaults = try self.allocator.dupe(?*Node, struct_decl.field_defaults); const display_name = try self.allocator.dupe(u8, alias_name); const si2 = StructInfo{ .field_names = struct_decl.field_names, .field_types = build.field_sx_types, .field_defaults = resolved_defaults, .llvm_type = build.llvm_type, .display_name = display_name, }; try self.type_registry.put(mangled_name, .{ .struct_info = si2 }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); return .{ .struct_type = mangled_name }; } fn registerInstantiatedTaggedEnum(self: *CodeGen, mangled_name: []const u8, union_decl: ast.EnumDecl) !Type { const build = try self.buildUnionFields(mangled_name, union_decl.variant_types); const tei = TaggedEnumInfo{ .variant_names = union_decl.variant_names, .variant_types = build.variant_sx_types, .llvm_type = build.llvm_type, .max_payload_size = build.max_payload_size, .payload_field_index = build.payload_field_index, }; try self.type_registry.put(mangled_name, .{ .tagged_enum = tei }); _ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name }); return .{ .union_type = mangled_name }; } /// Walk an AST body to find a struct declaration (from `return struct { ... }` or bare struct expr). fn findDeclInBody(comptime T: type, comptime tag: std.meta.FieldEnum(@TypeOf(@as(Node, undefined).data)), body: *Node) ?T { const extract = struct { fn get(node: *Node) ?T { return if (@field(node.data, @tagName(tag)) != @as(?T, null)) @field(node.data, @tagName(tag)) else null; } }; _ = extract; if (body.data == tag) return @field(body.data, @tagName(tag)); if (body.data == .block) { for (body.data.block.stmts) |stmt| { if (stmt.data == .return_stmt) { if (stmt.data.return_stmt.value) |val| { if (val.data == tag) return @field(val.data, @tagName(tag)); } } if (stmt.data == tag) return @field(stmt.data, @tagName(tag)); } } return null; } fn findStructInBody(_: *CodeGen, body: *Node) ?ast.StructDecl { return findDeclInBody(ast.StructDecl, .struct_decl, body); } fn findUnionInBody(_: *CodeGen, body: *Node) ?ast.EnumDecl { // Tagged enums with payloads are now stored as .enum_decl with variant_types populated const isTaggedEnum = struct { fn check(node: *Node) ?ast.EnumDecl { if (node.data == .enum_decl and node.data.enum_decl.variant_types.len > 0) { return node.data.enum_decl; } return null; } }; if (isTaggedEnum.check(body)) |ed| return ed; const stmts = if (body.data == .block) body.data.block.stmts else return null; for (stmts) |stmt| { if (stmt.data == .return_stmt) { if (stmt.data.return_stmt.value) |val| { if (isTaggedEnum.check(val)) |ed| return ed; } } if (isTaggedEnum.check(stmt)) |ed| return ed; } return null; } fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8, is_foreign: bool) !c.LLVMTypeRef { const ret_sx_type = self.resolveType(return_type); const is_main = std.mem.eql(u8, name, "main"); const ret_llvm_type = if (is_main) self.i32Type() else if (is_foreign and ret_sx_type.isStruct()) self.getForeignReturnABIType(ret_sx_type) else self.typeToLLVM(ret_sx_type); var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; for (params) |param| { if (param.is_comptime) continue; if (param.is_variadic) { // Variadic param becomes a slice {ptr, i32} in the LLVM signature try param_llvm_types.append(self.allocator, self.getStringStructType()); } else { const sx_ty = self.resolveType(param.type_expr); if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' in function '{s}' has unresolved type", .{ param.name, name }); // Foreign functions: apply C ABI lowering if (is_foreign and sx_ty == .string_type) { try param_llvm_types.append(self.allocator, self.ptrType()); } else if (is_foreign and sx_ty.isStruct()) { try param_llvm_types.append(self.allocator, self.getForeignParamABIType(sx_ty)); } else if (is_foreign and sx_ty.isArray()) { // [N]T → pointer in C ABI (C arrays decay to pointers) try param_llvm_types.append(self.allocator, self.ptrType()); } else { try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); } } } const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); return c.LLVMFunctionType( ret_llvm_type, if (params_slice.len > 0) params_slice.ptr else null, @intCast(params_slice.len), 0, ); } /// For foreign (C ABI) functions, struct parameters must be lowered to their /// ABI-equivalent types. LLVM does NOT do this automatically on all targets. /// Dispatches to architecture-specific lowering based on target config. fn getForeignParamABIType(self: *CodeGen, sx_ty: Type) c.LLVMTypeRef { if (!sx_ty.isStruct()) return self.typeToLLVM(sx_ty); const sname = self.resolveAlias(sx_ty.struct_type); const info = self.lookupStructInfo(sname) orelse return self.typeToLLVM(sx_ty); if (self.target_config.isAarch64()) { return self.aarch64ParamABI(info); } else if (self.target_config.isX86_64()) { if (self.target_config.isWindows()) { return self.win64ParamABI(info); } return self.x86_64SysVParamABI(info); } // Unknown architecture: pass struct type as-is (let LLVM backend handle it) return info.llvm_type; } /// AArch64 ABI: struct parameter lowering. /// - HFA (1-4 same float/double fields): [N x float/double] /// - Non-HFA ≤ 8 bytes: i64 /// - Non-HFA 9-16 bytes: [2 x i64] /// - > 16 bytes: pass as-is (indirect, not yet fully handled) fn aarch64ParamABI(self: *CodeGen, info: StructInfo) c.LLVMTypeRef { // Check HFA: 1-4 fields all of the same float type const field_types = info.field_types; if (field_types.len >= 1 and field_types.len <= 4) { const first = field_types[0]; if (first == .f32 or first == .f64) { var all_same = true; for (field_types[1..]) |ft| { if (!ft.eql(first)) { all_same = false; break; } } if (all_same) { const elem_ty = if (first == .f32) self.f32Type() else self.f64Type(); return c.LLVMArrayType2(elem_ty, @intCast(field_types.len)); } } } // Non-HFA: pack into integer registers const size = self.getTypeSize(info.llvm_type); if (size <= 8) return self.i64Type(); if (size <= 16) return c.LLVMArrayType2(self.i64Type(), 2); return info.llvm_type; } /// x86-64 SysV ABI: struct parameter lowering. /// Each 8-byte "eightbyte" is classified as INTEGER or SSE: /// - If all fields in the eightbyte are float/double: SSE (passed in XMM register) /// - If any field is integer/pointer: INTEGER (passed in GPR) /// - Structs > 16 bytes: passed in memory (by pointer) fn x86_64SysVParamABI(self: *CodeGen, info: StructInfo) c.LLVMTypeRef { const data_layout = c.LLVMGetModuleDataLayout(self.module); const size = self.getTypeSize(info.llvm_type); // > 16 bytes: MEMORY class (passed by pointer, handled by LLVM backend) if (size > 16) return info.llvm_type; // Single eightbyte (≤ 8 bytes) if (size <= 8) { return self.classifyEightbyte(info.field_types, size); } // Two eightbytes (9-16 bytes): classify each half independently // Split fields into first eightbyte (offset < 8) and second eightbyte (offset >= 8) var first_eb_types = std.ArrayList(Type).empty; var second_eb_types = std.ArrayList(Type).empty; var second_eb_size: u64 = 0; const struct_ty = info.llvm_type; for (info.field_types, 0..) |ft, idx| { const offset = c.LLVMOffsetOfElement(data_layout, struct_ty, @intCast(idx)); if (offset < 8) { first_eb_types.append(self.allocator, ft) catch return info.llvm_type; } else { second_eb_types.append(self.allocator, ft) catch return info.llvm_type; const field_llvm = self.typeToLLVM(ft); second_eb_size += self.getTypeSize(field_llvm); } } const eb1 = self.classifyEightbyte(first_eb_types.items, 8); const eb2 = self.classifyEightbyte(second_eb_types.items, if (second_eb_size > 0) second_eb_size else size - 8); // Compose the two eightbytes into a struct type var members: [2]c.LLVMTypeRef = .{ eb1, eb2 }; return c.LLVMStructTypeInContext(self.context, &members, 2, 0); } /// Classify a single x86-64 eightbyte: if all fields are float, return SSE type; /// otherwise return an integer type matching the byte size. fn classifyEightbyte(self: *CodeGen, field_types_in_eb: []const Type, byte_size: u64) c.LLVMTypeRef { if (field_types_in_eb.len == 0) { // No fields in this chunk — use integer padding return c.LLVMIntTypeInContext(self.context, @intCast(byte_size * 8)); } // Check if all fields are SSE (float/double) var all_sse = true; var float_count: u32 = 0; var double_count: u32 = 0; for (field_types_in_eb) |ft| { if (ft == .f32) { float_count += 1; } else if (ft == .f64) { double_count += 1; } else { all_sse = false; break; } } if (all_sse) { // SSE class: return appropriate float type if (double_count > 0 and float_count == 0) { if (double_count == 1) return self.f64Type(); // Multiple doubles shouldn't fit in one eightbyte (double = 8 bytes) return self.f64Type(); } if (float_count > 0 and double_count == 0) { if (float_count == 1) return self.f32Type(); // 2 floats = 8 bytes, fits in one eightbyte return c.LLVMArrayType2(self.f32Type(), @intCast(float_count)); } // Mixed float/double in one eightbyte shouldn't happen (float=4, double=8) // but fall through to integer just in case } // INTEGER class: coerce to integer matching the byte size return c.LLVMIntTypeInContext(self.context, @intCast(byte_size * 8)); } /// Windows x64 ABI: struct parameter lowering. /// Only structs of exactly 1, 2, 4, or 8 bytes are passed in a register. /// Everything else is passed by pointer (handled by LLVM backend). fn win64ParamABI(self: *CodeGen, info: StructInfo) c.LLVMTypeRef { const size = self.getTypeSize(info.llvm_type); // Windows x64: only power-of-2 sizes ≤ 8 passed in register if (size == 1 or size == 2 or size == 4 or size == 8) { return c.LLVMIntTypeInContext(self.context, @intCast(size * 8)); } // All other sizes: passed by pointer (LLVM handles byval) return info.llvm_type; } /// For foreign functions returning structs, apply the same ABI lowering as parameters. /// The rules for return values match parameter rules on both AArch64 and x86-64 SysV /// for small structs (≤ 16 bytes). Larger structs use sret (handled by LLVM). fn getForeignReturnABIType(self: *CodeGen, sx_ty: Type) c.LLVMTypeRef { // Reuse the same classification as parameters — the rules are identical // for small struct returns on both AArch64 and x86-64 SysV. return self.getForeignParamABIType(sx_ty); } /// Convert a struct value to its C ABI representation for a foreign call. /// Stores the struct to memory, then loads as the ABI type. fn convertStructToABI(self: *CodeGen, struct_val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef, abi_ty: c.LLVMTypeRef) c.LLVMValueRef { const struct_size = self.getTypeSize(struct_ty); const abi_size = self.getTypeSize(abi_ty); if (struct_size == abi_size) { // Same size (e.g. {float, float} → [2 x float]): store and reload const alloca = self.buildEntryBlockAlloca(struct_ty, "abi_tmp"); _ = c.LLVMBuildStore(self.builder, struct_val, alloca); return c.LLVMBuildLoad2(self.builder, abi_ty, alloca, "abi_arg"); } else { // Struct smaller than ABI type (e.g. {i8,i8,i8,i8} → i64): zero-init, then store struct const alloca = self.buildEntryBlockAlloca(abi_ty, "abi_tmp"); self.storeNull(abi_ty, alloca); _ = c.LLVMBuildStore(self.builder, struct_val, alloca); return c.LLVMBuildLoad2(self.builder, abi_ty, alloca, "abi_arg"); } } fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { const is_foreign = fd.body.data == .foreign_expr; const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign); const name_z = try self.allocator.dupeZ(u8, llvm_name); _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); // Track foreign functions for ABI lowering at call sites if (is_foreign) try self.foreign_fns.put(llvm_name, {}); // Track resolved parameter types for accurate call-site conversion var param_types = std.ArrayList(Type).empty; for (fd.params) |param| { if (param.is_comptime) continue; if (param.is_variadic) { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name } }); } else { try param_types.append(self.allocator, self.resolveType(param.type_expr)); } } try self.fn_param_types.put(llvm_name, try param_types.toOwnedSlice(self.allocator)); // Track declared return type (preserves signedness lost by LLVM round-trip) const ret_ty = if (fd.return_type) |rt| self.resolveType(rt) else Type.void_type; try self.function_return_types.put(llvm_name, ret_ty); // Track variadic function info for call site packing for (fd.params, 0..) |param, i| { if (param.is_variadic) { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; try self.variadic_functions.put(llvm_name, .{ .fixed_param_count = @intCast(i), .element_type_name = elem_name, }); break; } } } fn registerNamespace(self: *CodeGen, ns: ast.NamespaceDecl) !void { try self.namespaces.put(ns.name, {}); for (ns.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); try self.builtin_functions.put(qualified, {}); continue; } const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); if (fd.body.data == .foreign_expr) { // External C function in namespace — register LLVM declaration with C name only try self.registerFnDecl(fd, fd.name); // Also track qualified name as foreign for ABI lowering at call sites try self.foreign_fns.put(qualified, {}); // Store param types under qualified name so call-site type resolution works var param_types = std.ArrayList(Type).empty; for (fd.params) |param| { if (param.is_comptime) continue; try param_types.append(self.allocator, self.resolveType(param.type_expr)); } try self.fn_param_types.put(qualified, try param_types.toOwnedSlice(self.allocator)); } else if (fd.type_params.len > 0) { try self.generic_templates.put(qualified, fd); } else { try self.registerFnDecl(fd, qualified); } }, .enum_decl => |ed| { if (ed.variant_types.len > 0) { // Tagged enum with payloads try self.registerTaggedEnum(ed); const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); try self.type_registry.put(qualified_u, .{ .alias = ed.name }); } else { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names }); _ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified }); if (ed.backing_type) |bt_node| { const bt = self.resolveType(bt_node); try self.enum_backing_types.put(qualified, self.typeToLLVM(bt)); } } }, .struct_decl => |sd| { try self.registerStructType(sd); // Register qualified alias so rl.Color resolves to Color const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name }); try self.type_registry.put(qualified_s, .{ .alias = sd.name }); }, .union_decl => |ud| { try self.registerUnionType(ud); const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ud.name }); try self.type_registry.put(qualified_u, .{ .alias = ud.name }); }, .const_decl => |cd| { if (cd.value.data == .builtin_expr) { // #builtin constant in namespace — skip codegen } else if (cd.value.data == .lambda) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); try self.registerLambdaAsFunction(qualified, cd.value.data.lambda); } else if (cd.value.data == .type_expr) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name }); } }, .library_decl => |ld| { try self.foreign_libraries.append(self.allocator, ld.lib_name); }, .var_decl => |vd| { try self.registerGlobalVar(vd); }, else => {}, } } } fn genNamespaceBodies(self: *CodeGen, ns: ast.NamespaceDecl) !void { const saved_ns = self.current_namespace; self.current_namespace = ns.name; defer self.current_namespace = saved_ns; for (ns.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) { // skip — no body to generate } else if (fd.type_params.len == 0) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); if (shouldDeferFnBody(fd)) { try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ns.name }); } else { try self.genFnBody(fd, qualified); } } }, .const_decl => |cd| { if (cd.value.data == .lambda) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); try self.genLambdaBody(qualified, cd.value.data.lambda); } }, else => {}, } } } fn inferComptimeReturnType(self: *CodeGen, expr: *Node) Type { // xx: see through to inner expression if (expr.data == .unary_op and expr.data.unary_op.op == .xx) { return self.inferComptimeReturnType(expr.data.unary_op.operand); } // For function calls, look up the registered function's return type if (expr.data == .call) { if (self.resolveCalleeName(expr.data.call)) |callee_name| { var cnbuf: [256]u8 = undefined; const callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)) orelse return Type.s(64); const fn_type = c.LLVMGlobalGetValueType(callee_fn); const ret_llvm = c.LLVMGetReturnType(fn_type); return self.llvmTypeToSxType(ret_llvm); } } return self.inferType(expr); } /// Map an LLVM type back to a sx Type fn llvmTypeToSxType(self: *CodeGen, llvm_ty: c.LLVMTypeRef) Type { if (llvm_ty == self.i1Type()) return .boolean; if (llvm_ty == self.i8Type()) return Type.s(8); if (llvm_ty == c.LLVMInt16TypeInContext(self.context)) return Type.s(16); if (llvm_ty == self.i32Type()) return Type.s(32); if (llvm_ty == self.i64Type()) return Type.s(64); if (llvm_ty == self.f32Type()) return .f32; if (llvm_ty == self.f64Type()) return .f64; if (llvm_ty == self.voidType()) return .void_type; if (llvm_ty == self.getStringStructType()) return .string_type; if (self.any_struct_type != null and llvm_ty == self.any_struct_type.?) return .any_type; if (llvm_ty == self.ptrType()) return .string_type; // raw ptr fallback (meta_type) // Handle arbitrary-width integer types (e.g. i3, i7, i12) if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMIntegerTypeKind) { const width = c.LLVMGetIntTypeWidth(llvm_ty); if (width > 0 and width <= 64) return Type.s(@intCast(width)); } // Check for named struct types if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMStructTypeKind) { const name_ptr = c.LLVMGetStructName(llvm_ty); if (name_ptr != null) { const name = std.mem.span(name_ptr); if (self.type_registry.get(name)) |e| switch (e) { .struct_info => return .{ .struct_type = name }, .tagged_enum => return .{ .union_type = name }, else => {}, }; } } // Check for array types if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMArrayTypeKind) { const elem_llvm = c.LLVMGetElementType(llvm_ty); const length: u32 = @intCast(c.LLVMGetArrayLength2(llvm_ty)); const elem_ty = self.llvmTypeToSxType(elem_llvm); const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .array_type = .{ .element_name = elem_name, .length = length } }; } // Check for vector types if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMVectorTypeKind) { const elem_llvm = c.LLVMGetElementType(llvm_ty); const length = c.LLVMGetVectorSize(llvm_ty); const elem_ty = self.llvmTypeToSxType(elem_llvm); const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .vector_type = .{ .element_name = elem_name, .length = length } }; } return Type.s(64); } fn registerComptimeGlobal(self: *CodeGen, name: []const u8, expr: *Node, type_override: ?Type) !void { const ty = type_override orelse self.inferComptimeReturnType(expr); if (ty == .void_type) return self.emitErrorFmt("cannot infer type for comptime global '{s}'", .{name}); const llvm_ty = self.typeToLLVM(ty); const name_z = try self.allocator.dupeZ(u8, name); const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr); c.LLVMSetInitializer(global, c.LLVMConstInt(llvm_ty, 0, 0)); try self.comptime_globals.put(name, .{ .global = global, .ty = ty, .expr = expr }); } /// Evaluate a simple constant expression to an LLVM constant value. /// Returns null for expressions that can't be constant-folded at registration time. fn evalConstant(self: *CodeGen, node: *Node, target_ty: Type) ?c.LLVMValueRef { const llvm_ty = self.typeToLLVM(target_ty); switch (node.data) { .int_literal => |lit| { return c.LLVMConstInt(llvm_ty, @bitCast(@as(i64, lit.value)), 0); }, .float_literal => |lit| { return c.LLVMConstReal(llvm_ty, lit.value); }, .bool_literal => |lit| { return c.LLVMConstInt(llvm_ty, if (lit.value) 1 else 0, 0); }, else => return null, } } /// Register a top-level value constant (e.g., `SPECIAL_VALUE :u8: 42;`) as an LLVM global. fn registerTopLevelConstant(self: *CodeGen, cd: ast.ConstDecl) !void { const ta = cd.type_annotation orelse return; // need explicit type for top-level constants const sx_ty = self.resolveType(ta); if (sx_ty == .void_type) return; const const_val = self.evalConstant(cd.value, sx_ty) orelse return; const name_z = try self.allocator.dupeZ(u8, cd.name); const global = c.LLVMAddGlobal(self.module, self.typeToLLVM(sx_ty), name_z.ptr); c.LLVMSetInitializer(global, const_val); c.LLVMSetGlobalConstant(global, 1); try self.comptime_globals.put(cd.name, .{ .global = global, .ty = sx_ty, .expr = cd.value, .is_resolved = true, }); } fn registerGlobalVar(self: *CodeGen, vd: ast.VarDecl) !void { const ta = vd.type_annotation orelse return; const sx_ty = self.resolveType(ta); if (sx_ty == .void_type) return; const llvm_ty = self.typeToLLVM(sx_ty); const name_z = try self.allocator.dupeZ(u8, vd.name); const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr); // Initialize with undef (will be set at runtime, e.g. by load_gl) c.LLVMSetInitializer(global, self.getUndef(llvm_ty)); // NOT constant — this is a mutable global c.LLVMSetGlobalConstant(global, 0); try self.global_mutable_vars.put(vd.name, .{ .ptr = global, .ty = sx_ty }); } fn bindParam(self: *CodeGen, function: c.LLVMValueRef, name: []const u8, sx_ty: Type, param_idx: u32) !void { const llvm_ty = self.typeToLLVM(sx_ty); const param_name_z = try self.allocator.dupeZ(u8, name); const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); const param_val = c.LLVMGetParam(function, param_idx); _ = c.LLVMBuildStore(self.builder, param_val, alloca); try self.named_values.put(name, .{ .ptr = alloca, .ty = sx_ty }); } /// Prepare a return value: load structs/unions from alloca pointers, convert types. fn prepareReturnValue(self: *CodeGen, raw_val: c.LLVMValueRef, ret_type: Type) !c.LLVMValueRef { if (ret_type.isStruct()) { const sname = self.resolveAlias(ret_type.struct_type); const info = try self.getStructInfo(sname); return c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval"); } else if (ret_type.isUnion()) { const uname = ret_type.union_type; const resolved = self.resolveAlias(uname); const info = try self.getTaggedEnumInfo(resolved); return self.loadIfPointer(raw_val, info.llvm_type, "retval"); } else { const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(raw_val)); return self.convertValue(raw_val, src_ty, ret_type); } } fn genFnBody(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { self.named_values.clearRetainingCapacity(); const ret_sx_type = self.resolveType(fd.return_type); const is_main = std.mem.eql(u8, llvm_name, "main"); const ret_llvm_type = if (is_main) self.i32Type() else self.typeToLLVM(ret_sx_type); self.current_return_type = if (is_main) Type.s(32) else ret_sx_type; const name_z = try self.allocator.dupeZ(u8, llvm_name); const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse return self.emitErrorFmt("function '{s}' not found in LLVM module", .{llvm_name}); self.current_function = function; _ = self.appendBlock(function, "entry"); // Create allocas for parameters and store incoming values for (fd.params, 0..) |param, i| { const sx_ty = if (param.is_variadic) blk: { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; break :blk Type{ .slice_type = .{ .element_name = elem_name } }; } else self.resolveType(param.type_expr); if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' has unresolved type", .{param.name}); try self.bindParam(function, param.name, sx_ty, @intCast(i)); } // Push function-level scope so that function-body defers are tracked try self.pushScope(); // Generate body const body = fd.body; if (body.data != .block) return self.emitError("function body must be a block"); var last_val: c.LLVMValueRef = null; for (body.data.block.stmts) |stmt| { last_val = try self.genStmt(stmt); } // Return — skip if current block already has a terminator (from explicit return) const current_bb = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { // Implicit return path: pop scope (executes defers) then return try self.popScope(); // Check if last_val is void-typed (e.g. call to void-returning function) const effective_last_val: ?c.LLVMValueRef = if (last_val) |val| (if (c.LLVMTypeOf(val) == self.voidType()) null else val) else null; if (ret_sx_type == .void_type and !is_main) { self.retVoid(); } else if (effective_last_val) |val| { const ret_val = try self.prepareReturnValue(val, ret_sx_type); self.ret(ret_val); } else if (is_main) { self.ret(c.LLVMConstInt(ret_llvm_type, 0, 0)); } else if (ret_sx_type != .void_type) { _ = c.LLVMBuildUnreachable(self.builder); } else { self.retVoid(); } } else { // Explicit return already emitted defers; just clean up scope stacks if (self.scope_stack.items.len > 0) _ = self.scope_stack.pop(); } } fn registerLambdaAsFunction(self: *CodeGen, name: []const u8, lambda: ast.Lambda) !void { const ret_sx_type = self.inferType(lambda.body); const ret_llvm_type = self.typeToLLVM(ret_sx_type); var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; for (lambda.params) |param| { const sx_ty = self.resolveType(param.type_expr); try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); } const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); const fn_type = c.LLVMFunctionType( ret_llvm_type, if (params_slice.len > 0) params_slice.ptr else null, @intCast(params_slice.len), 0, ); const name_z = try self.allocator.dupeZ(u8, name); _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); } fn genLambdaBody(self: *CodeGen, name: []const u8, lambda: ast.Lambda) !void { self.named_values.clearRetainingCapacity(); const ret_sx_type = self.inferType(lambda.body); self.current_return_type = ret_sx_type; const name_z = try self.allocator.dupeZ(u8, name); const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse return self.emitErrorFmt("lambda '{s}' not found in LLVM module", .{name}); self.current_function = function; _ = self.appendBlock(function, "entry"); for (lambda.params, 0..) |param, i| { const sx_ty = self.resolveType(param.type_expr); try self.bindParam(function, param.name, sx_ty, @intCast(i)); } const ret_val = try self.genExpr(lambda.body); if (ret_val) |val| { const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); const converted = self.convertValue(val, src_ty, ret_sx_type); self.ret(converted); } else { self.retVoid(); } } fn genStmt(self: *CodeGen, node: *Node) !c.LLVMValueRef { self.current_span = node.span; switch (node.data) { .var_decl => |vd| { return self.genVarDecl(vd); }, .const_decl => |cd| { return self.genConstDecl(cd); }, .fn_decl => |fd| { // Local declaration inside a function body if (fd.type_params.len > 0) { // Generic template / type function: register for lazy instantiation try self.generic_templates.put(fd.name, fd); } else { // Non-generic local function // Save outer function state const saved_fn = self.current_function; const saved_bb = self.getCurrentBlock(); const saved_ret = self.current_return_type; const saved_named = self.named_values; self.named_values = std.StringHashMap(NamedValue).init(self.allocator); // Infer return type from body for => lambdas without explicit annotation const ret_sx_type = if (fd.return_type != null) self.resolveType(fd.return_type) else if (fd.is_arrow) self.inferType(fd.body) else Type.void_type; // For arrow lambdas with inferred return type, build function manually if (fd.is_arrow and fd.return_type == null) { const ret_llvm_type = self.typeToLLVM(ret_sx_type); var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; for (fd.params) |param| { try param_llvm_types.append(self.allocator, self.typeToLLVM(self.resolveType(param.type_expr))); } const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); const fn_type = c.LLVMFunctionType( ret_llvm_type, if (params_slice.len > 0) params_slice.ptr else null, @intCast(params_slice.len), 0, ); const name_z2 = try self.allocator.dupeZ(u8, fd.name); _ = c.LLVMAddFunction(self.module, name_z2.ptr, fn_type); try self.function_return_types.put(fd.name, ret_sx_type); } else { try self.registerFnDecl(fd, fd.name); } self.current_return_type = ret_sx_type; const name_z = try self.allocator.dupeZ(u8, fd.name); const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse return self.emitErrorFmt("local function '{s}' not found", .{fd.name}); self.current_function = function; _ = self.appendBlock(function, "entry"); for (fd.params, 0..) |param, i| { const sx_ty = self.resolveType(param.type_expr); try self.bindParam(function, param.name, sx_ty, @intCast(i)); } var last_val: c.LLVMValueRef = null; if (fd.body.data == .block) { for (fd.body.data.block.stmts) |stmt| { last_val = try self.genStmt(stmt); } } else { last_val = try self.genExpr(fd.body); } const current_bb2 = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb2) == null) { if (ret_sx_type == .void_type) { self.retVoid(); } else if (last_val) |val| { const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); const converted = self.convertValue(val, src_ty, ret_sx_type); self.ret(converted); } else { self.retVoid(); } } // Restore outer function state self.named_values = saved_named; self.current_return_type = saved_ret; self.current_function = saved_fn; self.positionAt(saved_bb); } return null; }, .struct_decl => |sd| { try self.registerStructType(sd); return null; }, .union_decl => { // C-style union — registration handled in type pass return null; }, .assignment => |asgn| { return self.genAssignment(asgn); }, .multi_assign => |ma| { return self.genMultiAssign(ma); }, .return_stmt => |rs| { // Evaluate return value first, then emit all defers, then return if (rs.value) |val_node| { const raw_val = try self.genExpr(val_node); const ret_val = try self.prepareReturnValue(raw_val, self.current_return_type); try self.emitAllDefers(); self.ret(ret_val); } else { try self.emitAllDefers(); self.retVoid(); } // Create a dead basic block for any subsequent instructions _ = self.appendBlock(self.current_function, "after_ret"); return null; }, .defer_stmt => |ds| { // Don't generate now — push onto current defer list for later execution if (self.scope_stack.items.len > 0) { const top = &self.scope_stack.items[self.scope_stack.items.len - 1].defers; try top.append(self.allocator, ds.expr); } return null; }, .insert_expr => |ins| { // Substitute comptime param nodes before evaluation (e.g., replace $fmt identifier with literal) const expr = if (self.comptime_param_nodes != null) try self.substituteComptimeNodes(ins.expr) else ins.expr; // Evaluate the inner expression via bytecode VM to get a string, parse it, generate inline const result = try self.comptimeEval(expr, .string_type); const code_z = try self.allocator.dupeZ(u8, result.string_val); var parser = Parser.init(self.allocator, code_z); var last_val: c.LLVMValueRef = null; while (parser.current.tag != .eof) { const stmt = try parser.parseStmt(); last_val = try self.genStmt(stmt); } return last_val; }, else => { return self.genExpr(node); }, } } fn genVarDecl(self: *CodeGen, vd: ast.VarDecl) !c.LLVMValueRef { // Meta type variable: x := f64 or x := Vec4 → runtime string holding the type name if (vd.value) |val| { const meta_name = self.asTypeName(val); if (meta_name) |raw_name| { const type_name = try self.allocator.dupeZ(u8, raw_name); const name_z = try self.allocator.dupeZ(u8, vd.name); const ptr_ty = self.ptrType(); const alloca = self.buildEntryBlockAlloca(ptr_ty, name_z.ptr); const str_val = self.buildGlobalString(type_name.ptr, "type_name"); _ = c.LLVMBuildStore(self.builder, str_val, alloca); try self.registerVariable(vd.name, alloca, .{ .meta_type = .{ .name = raw_name } }); return null; } } var sx_ty: Type = Type.s(64); if (vd.type_annotation) |ta| { sx_ty = self.resolveType(ta); } else if (vd.value) |val| { // Infer type from value if (val.data == .struct_literal) { const sl = val.data.struct_literal; if (sl.struct_name) |name| { sx_ty = .{ .struct_type = name }; } else if (sl.type_expr) |te| { sx_ty = self.resolveType(te); } else { return self.emitError("cannot infer struct type from untyped struct literal"); } } else if (val.data == .array_literal and val.data.array_literal.type_expr != null) { sx_ty = self.resolveType(val.data.array_literal.type_expr); } else { sx_ty = self.inferType(val); } } else { return self.emitErrorFmt("variable '{s}' has no type annotation and no initializer", .{vd.name}); } // Struct-typed variable if (sx_ty.isStruct()) { // Resolve type aliases (e.g. Vec3 -> Vec__3_f32) const sname = self.resolveAlias(sx_ty.struct_type); sx_ty = .{ .struct_type = sname }; const info = try self.getStructInfo(sname); const alloca = try self.buildNamedAlloca(info.llvm_type, vd.name); if (vd.value == null) { // Default-init: per-field defaults or zero try self.genStructDefaultInit(alloca, info); } else if (vd.value.?.data == .undef_literal) { // Undef-init: entire struct is undefined self.storeUndef(info.llvm_type, alloca); } else if (vd.value.?.data == .struct_literal) { // Struct literal codegen returns an alloca — use it directly instead const lit_alloca = try self.genStructLiteral(vd.value.?.data.struct_literal, sname); try self.registerVariable(vd.name, lit_alloca, sx_ty); return null; } else if (vd.value.?.data == .call) { // Function call returning a struct — result is a value, store to alloca const val = try self.genExpr(vd.value.?); _ = c.LLVMBuildStore(self.builder, val, alloca); } else { // General expression (xx cast, identifier, etc.) — evaluate as target type const val = try self.genExprAsType(vd.value.?, sx_ty); _ = c.LLVMBuildStore(self.builder, val, alloca); } try self.registerVariable(vd.name, alloca, sx_ty); return null; } // Union-typed variable (tagged enum or C-style union) if (sx_ty.isUnion()) { const uname = self.resolveAlias(sx_ty.union_type); sx_ty = .{ .union_type = uname }; // C-style (untagged) union if (self.lookupUnionInfo(uname)) |info| { const alloca = try self.buildNamedAlloca(info.llvm_type, vd.name); if (vd.value == null) { self.storeNull(info.llvm_type, alloca); } else if (vd.value.?.data == .undef_literal) { self.storeUndef(info.llvm_type, alloca); } else { return self.emitErrorFmt("union '{s}' must be initialized with '---' or field assignment", .{uname}); } try self.registerVariable(vd.name, alloca, sx_ty); return null; } // Tagged enum const info = try self.getTaggedEnumInfo(uname); const alloca = try self.buildNamedAlloca(info.llvm_type, vd.name); if (vd.value == null) { // Zero-init: tag=0, payload zeroed self.storeNull(info.llvm_type, alloca); } else if (vd.value.?.data == .undef_literal) { self.storeUndef(info.llvm_type, alloca); } else if (vd.value.?.data == .enum_literal) { const el = vd.value.?.data.enum_literal; const lit_alloca = try self.genTaggedEnumLiteral(el, uname); try self.registerVariable(vd.name, lit_alloca, sx_ty); return null; } else if (vd.value.?.data == .call) { // Call returning a union — could be enum construction (alloca) or function call (value) const result = try self.genExpr(vd.value.?); _ = c.LLVMBuildStore(self.builder, self.loadIfPointer(result, info.llvm_type, "union_load"), alloca); } else { // Other expression — try genExprAsType const result = try self.genExprAsType(vd.value.?, sx_ty); _ = c.LLVMBuildStore(self.builder, self.loadIfPointer(result, info.llvm_type, "union_load"), alloca); } try self.registerVariable(vd.name, alloca, sx_ty); return null; } // Array-typed variable if (sx_ty.isArray()) { const arr_info = sx_ty.array_type; const llvm_arr_ty = self.typeToLLVM(sx_ty); const arr_alloca = try self.buildNamedAlloca(llvm_arr_ty, vd.name); if (vd.value == null) { self.storeNull(llvm_arr_ty, arr_alloca); } else if (vd.value.?.data == .undef_literal) { self.storeUndef(llvm_arr_ty, arr_alloca); } else if (vd.value.?.data == .array_literal) { const al = vd.value.?.data.array_literal; const elem_sx_ty = try self.resolveElementType(arr_info.element_name, "array"); const elem_llvm_ty = self.typeToLLVM(elem_sx_ty); const len = @min(al.elements.len, arr_info.length); for (0..len) |i| { const val = try self.genExprAsType(al.elements[i], elem_sx_ty); const gep = self.gepArrayElement(llvm_arr_ty, arr_alloca, self.constInt32(@intCast(i)), "arr_elem"); _ = c.LLVMBuildStore(self.builder, val, gep); } // Zero-init remaining elements for (len..arr_info.length) |i| { const gep = self.gepArrayElement(llvm_arr_ty, arr_alloca, self.constInt32(@intCast(i)), "arr_elem"); self.storeNull(elem_llvm_ty, gep); } } else { return self.emitErrorFmt("unsupported initializer for array variable '{s}'", .{vd.name}); } try self.registerVariable(vd.name, arr_alloca, sx_ty); return null; } // Vector-typed variable if (sx_ty.isVector()) { const llvm_vec_ty = self.typeToLLVM(sx_ty); const vec_alloca = try self.buildNamedAlloca(llvm_vec_ty, vd.name); if (vd.value == null) { self.storeNull(llvm_vec_ty, vec_alloca); } else if (vd.value.?.data == .undef_literal) { self.storeUndef(llvm_vec_ty, vec_alloca); } else if (vd.value.?.data == .array_literal) { const vec_val = try self.genVectorLiteral(vd.value.?.data.array_literal, sx_ty); _ = c.LLVMBuildStore(self.builder, vec_val, vec_alloca); } else { // Expression (e.g. function call) returning a vector const val = try self.genExpr(vd.value.?); _ = c.LLVMBuildStore(self.builder, val, vec_alloca); } try self.registerVariable(vd.name, vec_alloca, sx_ty); return null; } // Function pointer typed variable if (sx_ty.isFunctionType()) { const llvm_ty = self.ptrType(); const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); if (vd.value == null) { self.storeNull(llvm_ty, alloca); } else if (vd.value.?.data == .undef_literal) { self.storeUndef(llvm_ty, alloca); } else if (vd.value.?.data == .unary_op and vd.value.?.data.unary_op.op == .xx) { // xx cast: e.g. xx SDL_GL_GetProcAddress("glClear") const inner = vd.value.?.data.unary_op.operand; const val = try self.genExpr(inner); const src_ty = self.inferType(inner); const converted = self.convertValue(val, src_ty, sx_ty); _ = c.LLVMBuildStore(self.builder, converted, alloca); } else { // Direct assignment: identifier (function name) or other expression const val = try self.genExpr(vd.value.?); _ = c.LLVMBuildStore(self.builder, val, alloca); } try self.registerVariable(vd.name, alloca, sx_ty); return null; } // Guard: void type cannot be allocated (would crash LLVM) if (sx_ty == .void_type) { return self.emitErrorFmt("cannot declare variable '{s}' with void type", .{vd.name}); } // Non-struct types const llvm_ty = self.typeToLLVM(sx_ty); const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); if (vd.value == null) { // Default-init: zero self.storeNull(llvm_ty, alloca); } else if (vd.value.?.data == .undef_literal) { // Undef-init self.storeUndef(llvm_ty, alloca); } else { const val = vd.value.?; const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; const init_val = if (val.data == .enum_literal and enum_name != null) self.genEnumLiteral(val.data.enum_literal.name, enum_name.?) else if (vd.type_annotation != null) try self.genExprAsType(val, sx_ty) else try self.genExpr(val); _ = c.LLVMBuildStore(self.builder, init_val, alloca); } try self.registerVariable(vd.name, alloca, sx_ty); return null; } fn genStructDefaultInit(self: *CodeGen, alloca: c.LLVMValueRef, info: StructInfo) !void { for (info.field_names, 0..) |_, fi| { const ft = info.field_types[fi]; const ft_llvm = self.typeToLLVM(ft); const gep = self.structGEP(info.llvm_type, alloca, @intCast(fi), "dinit"); if (info.field_defaults.len > fi and info.field_defaults[fi] != null) { const default_node = info.field_defaults[fi].?; if (default_node.data == .undef_literal) { // Field default is --- → store undef self.storeUndef(ft_llvm, gep); } else { // Field has expression default → evaluate and convert const val = try self.genExprAsType(default_node, ft); if (ft.isStruct() or ft.isUnion()) { // Aggregate types: genExprAsType returns an alloca, load the value first const loaded = c.LLVMBuildLoad2(self.builder, ft_llvm, val, "dinit_load"); _ = c.LLVMBuildStore(self.builder, loaded, gep); } else { _ = c.LLVMBuildStore(self.builder, val, gep); } } } else { // No default → zero self.storeNull(ft_llvm, gep); } } } fn genConstDecl(self: *CodeGen, cd: ast.ConstDecl) !c.LLVMValueRef { // Compile-time evaluation: register as comptime global for JIT if (cd.value.data == .comptime_expr) { const ct_type_override: ?Type = if (cd.type_annotation) |te| Type.fromTypeExpr(te) else null; try self.registerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, ct_type_override); return null; } // Local lambda: register as function, generate body, done if (cd.value.data == .lambda) { const saved_fn = self.current_function; const saved_bb = self.getCurrentBlock(); const saved_ret = self.current_return_type; const saved_named = self.named_values; self.named_values = std.StringHashMap(NamedValue).init(self.allocator); try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); try self.genLambdaBody(cd.name, cd.value.data.lambda); self.named_values.deinit(); self.named_values = saved_named; self.current_return_type = saved_ret; self.current_function = saved_fn; self.positionAt(saved_bb); return null; } var sx_ty: Type = Type.s(64); if (cd.type_annotation) |ta| { sx_ty = self.resolveType(ta); } else { sx_ty = self.inferType(cd.value); } // Enum-typed constant: delegate to genExprAsType which handles enum_literal if (sx_ty.isUnion()) { const val = try self.genExprAsType(cd.value, sx_ty); try self.registerVariable(cd.name, val, sx_ty); return null; } // Function pointer typed constant if (sx_ty.isFunctionType()) { const llvm_ty = self.ptrType(); const alloca = try self.buildNamedAlloca(llvm_ty, cd.name); if (cd.value.data == .unary_op and cd.value.data.unary_op.op == .xx) { const inner = cd.value.data.unary_op.operand; const val = try self.genExpr(inner); const src_inner_ty = self.inferType(inner); const converted = self.convertValue(val, src_inner_ty, sx_ty); _ = c.LLVMBuildStore(self.builder, converted, alloca); } else { const val = try self.genExpr(cd.value); _ = c.LLVMBuildStore(self.builder, val, alloca); } try self.registerVariable(cd.name, alloca, sx_ty); return null; } const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; const init_val = if (cd.value.data == .enum_literal and enum_name != null) self.genEnumLiteral(cd.value.data.enum_literal.name, enum_name.?) else if (cd.type_annotation != null) try self.genExprAsType(cd.value, sx_ty) else try self.genExpr(cd.value); const llvm_ty = self.typeToLLVM(sx_ty); const alloca = try self.buildNamedAlloca(llvm_ty, cd.name); _ = c.LLVMBuildStore(self.builder, init_val, alloca); try self.registerVariable(cd.name, alloca, sx_ty); return null; } fn genCompoundOp(self: *CodeGen, op: ast.Assignment.Op, cur: c.LLVMValueRef, rhs: c.LLVMValueRef, ty: Type) c.LLVMValueRef { return switch (op) { .add_assign => if (ty.isFloat()) c.LLVMBuildFAdd(self.builder, cur, rhs, "addtmp") else c.LLVMBuildAdd(self.builder, cur, rhs, "addtmp"), .sub_assign => if (ty.isFloat()) c.LLVMBuildFSub(self.builder, cur, rhs, "subtmp") else c.LLVMBuildSub(self.builder, cur, rhs, "subtmp"), .mul_assign => if (ty.isFloat()) c.LLVMBuildFMul(self.builder, cur, rhs, "multmp") else c.LLVMBuildMul(self.builder, cur, rhs, "multmp"), .div_assign => if (ty.isFloat()) c.LLVMBuildFDiv(self.builder, cur, rhs, "divtmp") else if (ty.isUnsigned()) c.LLVMBuildUDiv(self.builder, cur, rhs, "divtmp") else c.LLVMBuildSDiv(self.builder, cur, rhs, "divtmp"), .mod_assign => if (ty.isFloat()) c.LLVMBuildFRem(self.builder, cur, rhs, "modtmp") else if (ty.isUnsigned()) c.LLVMBuildURem(self.builder, cur, rhs, "modtmp") else c.LLVMBuildSRem(self.builder, cur, rhs, "modtmp"), .assign => unreachable, }; } fn storeOrCompound(self: *CodeGen, op: ast.Assignment.Op, ptr: c.LLVMValueRef, rhs: c.LLVMValueRef, field_ty: Type, name: [*c]const u8) void { if (op == .assign) { _ = c.LLVMBuildStore(self.builder, rhs, ptr); } else { const llvm_ty = self.typeToLLVM(field_ty); const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, name); _ = c.LLVMBuildStore(self.builder, self.genCompoundOp(op, cur, rhs, field_ty), ptr); } } fn genAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { // Field assignment: expr.field = value; if (asgn.target.data == .field_access) { return self.genFieldAssignment(asgn); } // Index assignment: expr[i] = value; if (asgn.target.data == .index_expr) { return self.genIndexAssignment(asgn); } // Deref assignment: p.* = value; if (asgn.target.data == .deref_expr) { const de = asgn.target.data.deref_expr; const ptr_val = try self.genExpr(de.operand); const ptr_ty = self.inferType(de.operand); if (!ptr_ty.isPointer()) return self.emitError("dereference assignment requires a pointer"); const pointee_ty = self.resolveTypeFromName(ptr_ty.pointer_type.pointee_name) orelse return self.emitError("unknown pointee type"); const new_val = try self.genExprAsType(asgn.value, pointee_ty); _ = c.LLVMBuildStore(self.builder, new_val, ptr_val); return null; } // Target must be an identifier if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable"); const name = asgn.target.data.identifier.name; const lookup = self.lookupValue(name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{name}); const entry = lookup.asNamedValue() orelse return self.emitErrorFmt("cannot assign to constant '{s}'", .{name}); if (entry.is_const) return self.emitErrorFmt("cannot assign to '{s}'", .{name}); // Meta type reassignment: x = Vec4, x = f64, x = test if (entry.ty == .meta_type and asgn.op == .assign) { const raw_name = self.asTypeName(asgn.value) orelse blk: { // Also accept function names as meta_type values (use signature) if (asgn.value.data == .identifier) { const fn_name = asgn.value.data.identifier.name; if (self.fn_signatures.get(fn_name)) |sig| break :blk sig; } break :blk @as(?[]const u8, null); }; if (raw_name) |rn| { const type_name = try self.allocator.dupeZ(u8, rn); const str_val = self.buildGlobalString(type_name.ptr, "type_name"); _ = c.LLVMBuildStore(self.builder, str_val, entry.ptr); if (self.named_values.getPtr(name)) |entry_ptr| { entry_ptr.ty = .{ .meta_type = .{ .name = rn } }; } return null; } return self.emitErrorFmt("cannot assign non-type value to Type variable '{s}'", .{name}); } // Function pointer reassignment if (entry.ty.isFunctionType() and asgn.op == .assign) { if (asgn.value.data == .unary_op and asgn.value.data.unary_op.op == .xx) { const inner = asgn.value.data.unary_op.operand; const val = try self.genExpr(inner); const src_ty = self.inferType(inner); const converted = self.convertValue(val, src_ty, entry.ty); _ = c.LLVMBuildStore(self.builder, converted, entry.ptr); } else { const val = try self.genExpr(asgn.value); _ = c.LLVMBuildStore(self.builder, val, entry.ptr); } return null; } // Tagged enum reassignment: s = .circle(3.14) or s = .none or s = fn_call() if (entry.ty.isUnion() and asgn.op == .assign) { if (self.lookupTaggedEnumInfo(entry.ty.union_type)) |info| { const new_val = try self.genExprAsType(asgn.value, entry.ty); // genExprAsType returns alloca for enum literals, loaded value for calls _ = c.LLVMBuildStore(self.builder, self.loadIfPointer(new_val, info.llvm_type, "union_load"), entry.ptr); return null; } // C-style union: full assignment not supported, use field assignment } const new_val = try self.genExpr(asgn.value); const llvm_ty = self.typeToLLVM(entry.ty); const store_val = if (asgn.op == .assign) new_val else blk: { const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); break :blk self.genCompoundOp(asgn.op, cur, new_val, entry.ty); }; _ = c.LLVMBuildStore(self.builder, store_val, entry.ptr); return null; } fn genMultiAssign(self: *CodeGen, ma: ast.MultiAssign) !c.LLVMValueRef { const n = ma.targets.len; // Phase 1: Evaluate ALL RHS values into temporaries. // This ensures correctness for aliased swaps (a, b = b, a). const tmp_ptrs = try self.allocator.alloc(c.LLVMValueRef, n); const target_types = try self.allocator.alloc(Type, n); for (0..n) |i| { target_types[i] = self.inferType(ma.targets[i]); const llvm_ty = self.typeToLLVM(target_types[i]); const val = try self.genExprAsType(ma.values[i], target_types[i]); const tmp = self.buildEntryBlockAlloca(llvm_ty, "swap_tmp"); _ = c.LLVMBuildStore(self.builder, val, tmp); tmp_ptrs[i] = tmp; } // Phase 2: Load temporaries and store to each target. for (0..n) |i| { const llvm_ty = self.typeToLLVM(target_types[i]); const val = c.LLVMBuildLoad2(self.builder, llvm_ty, tmp_ptrs[i], "swap_load"); try self.storeToLvalue(ma.targets[i], val); } return null; } fn storeToLvalue(self: *CodeGen, target: *Node, val: c.LLVMValueRef) !void { // Deref assignment: p.* = val if (target.data == .deref_expr) { const de = target.data.deref_expr; const ptr_val = try self.genExpr(de.operand); _ = c.LLVMBuildStore(self.builder, val, ptr_val); return; } // Identifier assignment: x = val (with const check) if (target.data == .identifier) { const name = target.data.identifier.name; const lookup = self.lookupValue(name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{name}); const entry = lookup.asNamedValue() orelse return self.emitErrorFmt("cannot assign to constant '{s}'", .{name}); if (entry.is_const) return self.emitErrorFmt("cannot assign to '{s}'", .{name}); _ = c.LLVMBuildStore(self.builder, val, entry.ptr); return; } // Field access and index expressions — use genAddressOf to get the target pointer if (target.data == .field_access or target.data == .index_expr) { const ptr = try self.genAddressOf(target); _ = c.LLVMBuildStore(self.builder, val, ptr); return; } return self.emitError("multi-assign target must be a variable, field, index, or dereference expression"); } fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { const fa = asgn.target.data.field_access; // Object must be an identifier for now if (fa.object.data != .identifier) return self.emitError("field assignment target must be a variable"); const obj_name = fa.object.data.identifier.name; const entry = self.named_values.get(obj_name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{obj_name}); // Pointer auto-deref: p.field = val if (entry.ty.isPointer()) { const pointee_ty = self.resolveTypeFromName(entry.ty.pointer_type.pointee_name) orelse return self.emitError("unknown pointee type for field assignment"); if (pointee_ty.isStruct()) { const sname = pointee_ty.struct_type; const info = try self.getStructInfo(sname); const fi = try self.findFieldIndex(info.field_names, fa.field, sname); const field_ty = info.field_types[fi]; const loaded_ptr = c.LLVMBuildLoad2(self.builder, self.ptrType(), entry.ptr, "ptr_load"); const gep = self.structGEP(info.llvm_type, loaded_ptr, @intCast(fi), "pfield_ptr"); const rhs = try self.genExprAsType(asgn.value, field_ty); self.storeOrCompound(asgn.op, gep, rhs, field_ty, "pcur"); return null; } return self.emitError("field assignment through pointer requires a struct pointee"); } // C-style union field assignment if (entry.ty.isUnion()) { const uname = entry.ty.union_type; if (self.lookupUnionInfo(uname)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |fidx| { const field_ty = info.field_types[fidx]; const rhs = try self.genExprAsType(asgn.value, field_ty); self.storeOrCompound(asgn.op, entry.ptr, rhs, field_ty, "ucur"); return null; } // Check promoted fields from anonymous structs if (info.promoted_fields.get(fa.field)) |pf| { const sinfo = try self.getStructInfo(pf.struct_name); const gep = self.structGEP(sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), "promoted_ptr"); const rhs = try self.genExprAsType(asgn.value, pf.field_type); self.storeOrCompound(asgn.op, gep, rhs, pf.field_type, "ucur"); return null; } return self.emitErrorFmt("no field '{s}' in union '{s}'", .{ fa.field, uname }); } return self.emitErrorFmt("field assignment not supported on tagged enum '{s}'", .{uname}); } if (!entry.ty.isStruct()) return self.emitErrorFmt("field access on non-struct variable '{s}'", .{obj_name}); const sname = entry.ty.struct_type; const info = try self.getStructInfo(sname); const fi = try self.findFieldIndex(info.field_names, fa.field, sname); const field_ty = info.field_types[fi]; const gep = self.structGEP(info.llvm_type, entry.ptr, @intCast(fi), "fassign"); // Generate RHS and convert to field type const rhs = try self.genExprAsType(asgn.value, field_ty); self.storeOrCompound(asgn.op, gep, rhs, field_ty, "fcur"); return null; } fn genIndexAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { const ie = asgn.target.data.index_expr; const obj_ty = self.inferType(ie.object); if (obj_ty == .string_type) { // String index assignment: s[i] = c const str_val = try self.genExpr(ie.object); const ptr = self.extractValue(str_val, 0, "str_ptr"); const idx = try self.genExpr(ie.index); const val = try self.genExpr(asgn.value); const i8_type = self.i8Type(); const gep_ptr = self.gepPointerElement(i8_type, ptr, idx, "stridx"); const byte_val = self.trunc(val, i8_type, "trunc_byte"); _ = c.LLVMBuildStore(self.builder, byte_val, gep_ptr); return null; } if (obj_ty.isArray()) { if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { const idx = try self.genExpr(ie.index); const val = try self.genExpr(asgn.value); const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), entry.ptr, idx, "arridx"); _ = c.LLVMBuildStore(self.builder, val, gep_ptr); return null; } } // struct.field[i] = val — GEP through struct to array field, then index if (ie.object.data == .field_access) { const field_ptr = try self.genAddressOf(ie.object); const idx = try self.genExpr(ie.index); const val = try self.genExpr(asgn.value); const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), field_ptr, idx, "field_arridx"); _ = c.LLVMBuildStore(self.builder, val, gep_ptr); return null; } } if (obj_ty.isSlice()) { const slice_info = obj_ty.slice_type; const elem_ty = Type.fromName(slice_info.element_name) orelse return self.emitError("unknown slice element type"); const elem_llvm_ty = self.typeToLLVM(elem_ty); // Load slice value to get ptr const slice_val = blk: { if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { break :blk c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); } } break :blk try self.genExpr(ie.object); }; const ptr = self.extractValue(slice_val, 0, "slice_ptr"); const idx = try self.genExpr(ie.index); const val = try self.genExpr(asgn.value); const gep_ptr = self.gepPointerElement(elem_llvm_ty, ptr, idx, "sliceidx"); _ = c.LLVMBuildStore(self.builder, val, gep_ptr); return null; } // Many-pointer index assignment: mp[i] = val if (obj_ty.isManyPointer()) { const elem_ty = self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse return self.emitError("unknown many-pointer element type"); const elem_llvm_ty = self.typeToLLVM(elem_ty); const ptr_val = try self.genExpr(ie.object); const idx = try self.genExpr(ie.index); const val = try self.genExprAsType(asgn.value, elem_ty); const gep_ptr = self.gepPointerElement(elem_llvm_ty, ptr_val, idx, "mptridx"); _ = c.LLVMBuildStore(self.builder, val, gep_ptr); return null; } return self.emitError("index assignment requires a string, array, slice, or [*] pointer target"); } fn genExpr(self: *CodeGen, node: *Node) anyerror!c.LLVMValueRef { self.current_span = node.span; switch (node.data) { .int_literal => |lit| { const i64_type = self.i64Type(); return c.LLVMConstInt(i64_type, @bitCast(@as(i64, lit.value)), 0); }, .float_literal => |lit| { const f32_type = self.f32Type(); return c.LLVMConstReal(f32_type, lit.value); }, .bool_literal => |lit| { const i1_type = self.i1Type(); return c.LLVMConstInt(i1_type, if (lit.value) 1 else 0, 0); }, .string_literal => |lit| { const content = if (lit.is_raw) lit.raw else try unescape.unescapeString(self.allocator, lit.raw); const str_z = try self.allocator.dupeZ(u8, content); const ptr = self.buildGlobalString(str_z.ptr, "str"); return self.buildStringSlice(ptr, self.constInt64(@intCast(content.len))); }, .identifier => |ident| { if (self.lookupValue(ident.name)) |v| { switch (v) { .local => |nv| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(nv.ty), nv.ptr, "loadtmp"), .comptime_global => |ct| { if (!ct.is_resolved) try self.resolveComptimeGlobal(ct); return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ct.ty), ct.global, "ct_load"); }, .global_mutable => |gm| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(gm.ty), gm.ptr, "global_load"), } } // Fall back to function name → function pointer value { var nbuf: [256]u8 = undefined; var fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(ident.name, &nbuf)); if (fn_val == null) { // Try qualified name with current namespace if (self.current_namespace) |ns| { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, ident.name }); var qbuf: [256]u8 = undefined; fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf)); } } if (fn_val != null) return fn_val.?; } return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name}); }, .binary_op => |binop| { if (binop.op == .and_op) return self.genShortCircuitOp(binop, true); if (binop.op == .or_op) return self.genShortCircuitOp(binop, false); const lhs_ty = self.inferType(binop.lhs); const rhs_ty = self.inferType(binop.rhs); const result_type = Type.widen(lhs_ty, rhs_ty); // Tagged enum comparison: compare tags only if (result_type.isUnion() and (binop.op == .eq or binop.op == .neq)) { const uname = result_type.union_type; const resolved = self.resolveAlias(uname); const info = self.lookupTaggedEnumInfo(resolved) orelse return self.emitError("unknown tagged enum type"); const tag_ty = self.getEnumLLVMType(resolved); var lhs_val = try self.genExprAsType(binop.lhs, result_type); var rhs_val = try self.genExprAsType(binop.rhs, result_type); // If either side is a pointer (alloca from genTaggedEnumLiteral), load it lhs_val = self.loadIfPointer(lhs_val, info.llvm_type, "union_load_l"); rhs_val = self.loadIfPointer(rhs_val, info.llvm_type, "union_load_r"); // Extract tags (field 0) and compare const lhs_tag = self.extractValue(lhs_val, 0, "lhs_tag"); const rhs_tag = self.extractValue(rhs_val, 0, "rhs_tag"); _ = tag_ty; const pred: c_uint = if (binop.op == .eq) c.LLVMIntEQ else c.LLVMIntNE; return self.icmp(pred, lhs_tag, rhs_tag, "tag_cmp"); } const lhs = try self.genExprAsType(binop.lhs, result_type); const rhs = try self.genExprAsType(binop.rhs, result_type); return self.genBinaryOp(binop.op, lhs, rhs, result_type); }, .chained_comparison => |chain| { return self.genChainedComparison(chain); }, .unary_op => |unop| { if (unop.op == .xx) { // xx requires a target type context (assignment, declaration, argument, return) return self.emitError("'xx' cast requires a target type context"); } if (unop.op == .address_of) { return self.genAddressOf(unop.operand); } const operand = try self.genExpr(unop.operand); return switch (unop.op) { .negate => blk: { const operand_ty = self.inferType(unop.operand); if (operand_ty.isVector()) { const elem_ty = operand_ty.vectorElementType(); break :blk if (elem_ty != null and elem_ty.?.isFloat()) c.LLVMBuildFNeg(self.builder, operand, "vnegtmp") else c.LLVMBuildNeg(self.builder, operand, "vnegtmp"); } break :blk if (self.exprIsFloat(unop.operand)) c.LLVMBuildFNeg(self.builder, operand, "negtmp") else c.LLVMBuildNeg(self.builder, operand, "negtmp"); }, .not => c.LLVMBuildNot(self.builder, operand, "nottmp"), .xx, .address_of => unreachable, }; }, .enum_literal => |el| { if (self.current_return_type.isUnion()) { return self.genTaggedEnumLiteral(el, self.current_return_type.union_type); } if (self.current_return_type.isEnum()) { return self.genEnumLiteral(el.name, self.current_return_type.enum_type); } return self.emitError("cannot infer enum type for literal"); }, .struct_literal => |sl| { const ctx_name: ?[]const u8 = if (self.current_return_type.isStruct()) self.current_return_type.struct_type else null; return self.genStructLiteral(sl, ctx_name); }, .array_literal => |al| { // Typed array/vector/slice literal: Type.[elems] if (al.type_expr) |te| { const ty = self.resolveType(te); if (ty.isVector()) return self.genVectorLiteral(al, ty); if (ty.isArray()) return self.genArrayLiteral(al, ty); if (ty.isSlice()) return self.genSliceLiteral(al, ty); } // If current return type is vector, build as vector SSA value if (self.current_return_type.isVector()) { return self.genVectorLiteral(al, self.current_return_type); } return self.genArrayLiteral(al, null); }, .field_access => |fa| { return self.genFieldAccess(fa); }, .index_expr => |ie| { return self.genIndexExpr(ie); }, .slice_expr => |se| { return self.genSliceExpr(se); }, .call => |call_node| { return self.genCall(call_node); }, .if_expr => |ie| { return self.genIfExpr(ie); }, .match_expr => |me| { return self.genMatchExpr(me); }, .while_expr => |we| { return self.genWhileExpr(we); }, .for_expr => |fe| { return self.genForExpr(fe); }, .break_expr => { if (self.loop_break_bb) |break_bb| { self.br(break_bb); _ = self.appendBlock(self.current_function, "after_break"); return null; } return self.emitError("'break' outside of loop"); }, .continue_expr => { if (self.loop_continue_bb) |continue_bb| { self.br(continue_bb); _ = self.appendBlock(self.current_function, "after_continue"); return null; } return self.emitError("'continue' outside of loop"); }, .block => |blk| { try self.pushScope(); var last_val: c.LLVMValueRef = null; for (blk.stmts) |stmt| { last_val = try self.genStmt(stmt); } try self.popScope(); return last_val; }, .var_decl => |vd| { return self.genVarDecl(vd); }, .const_decl => |cd| { return self.genConstDecl(cd); }, .assignment => |asgn| { return self.genAssignment(asgn); }, .multi_assign => |ma| { return self.genMultiAssign(ma); }, .return_stmt => |rs| { if (rs.value) |val_node| { const raw_val = try self.genExpr(val_node); const ret_val = try self.prepareReturnValue(raw_val, self.current_return_type); try self.emitAllDefers(); self.ret(ret_val); } else { try self.emitAllDefers(); self.retVoid(); } _ = self.appendBlock(self.current_function, "after_ret"); return null; }, .deref_expr => |de| { const ptr_val = try self.genExpr(de.operand); const ptr_ty = self.inferType(de.operand); if (ptr_ty.isPointer()) { const pointee_ty = self.resolveTypeFromName(ptr_ty.pointer_type.pointee_name) orelse return self.emitError("unknown pointee type"); return self.loadTyped(pointee_ty, ptr_val, "deref"); } return self.emitError("dereference requires a pointer type"); }, .null_literal => { return c.LLVMConstNull(self.ptrType()); }, .comptime_expr => |ct| { return self.genExpr(ct.expr); }, else => return self.emitError("unsupported expression"), } } fn genAddressOf(self: *CodeGen, operand: *Node) !c.LLVMValueRef { // &x — return the alloca pointer of the variable if (operand.data == .identifier) { if (self.named_values.get(operand.data.identifier.name)) |entry| { return entry.ptr; } return self.emitErrorFmt("undefined variable '{s}'", .{operand.data.identifier.name}); } // &expr[i] — return GEP pointer to the indexed element if (operand.data == .index_expr) { const ie = operand.data.index_expr; const obj_ty = self.inferType(ie.object); const idx = try self.genExpr(ie.index); if (obj_ty.isArray()) { if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { return self.gepArrayElement(self.typeToLLVM(obj_ty), entry.ptr, idx, "addr_elem"); } } } if (obj_ty.isSlice() or obj_ty == .string_type) { const slice_val = try self.genExpr(ie.object); const ptr = self.extractValue(slice_val, 0, "slice_ptr"); const elem_ty = if (obj_ty.isSlice()) obj_ty.sliceElementType() orelse return self.emitError("unknown slice element type") else Type.u(8); return self.gepPointerElement(self.typeToLLVM(elem_ty), ptr, idx, "addr_elem"); } } // &s.field — return GEP pointer to the struct field if (operand.data == .field_access) { const fa = operand.data.field_access; if (fa.object.data == .identifier) { if (self.named_values.get(fa.object.data.identifier.name)) |entry| { if (entry.ty.isStruct()) { const sname = entry.ty.struct_type; const info = try self.getStructInfo(sname); const idx = try self.findFieldIndex(info.field_names, fa.field, sname); return self.structGEP(info.llvm_type, entry.ptr, @intCast(idx), "addr_field"); } // &u.field where u is a C-style union — all fields at offset 0 if (entry.ty.isUnion()) { if (self.lookupUnionInfo(entry.ty.union_type)) |info| { if (self.findNameIndex(info.field_names, fa.field) != null) { return entry.ptr; } if (info.promoted_fields.get(fa.field)) |pf| { const sinfo = try self.getStructInfo(pf.struct_name); return self.structGEP(sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), "addr_promoted"); } } } // &p.field where p is *Struct — auto-deref through pointer if (entry.ty.isPointer()) { const pointee_name = entry.ty.pointer_type.pointee_name; if (self.lookupStructInfo(pointee_name)) |info| { const loaded_ptr = c.LLVMBuildLoad2(self.builder, self.ptrType(), entry.ptr, "ptr_load"); const idx = try self.findFieldIndex(info.field_names, fa.field, pointee_name); return self.structGEP(info.llvm_type, loaded_ptr, @intCast(idx), "addr_pfield"); } } } } } return self.emitError("address-of requires a variable, index, or field expression"); } const StructBuildResult = struct { field_sx_types: []const Type, llvm_type: c.LLVMTypeRef, }; fn buildStructFields(self: *CodeGen, name: []const u8, field_type_nodes: []const *Node) !StructBuildResult { var field_sx_types = std.ArrayList(Type).empty; var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; for (field_type_nodes) |ft| { const sx_ty = self.resolveType(ft); try field_sx_types.append(self.allocator, sx_ty); try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); } const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); const name_z = try self.allocator.dupeZ(u8, name); const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); return .{ .field_sx_types = try field_sx_types.toOwnedSlice(self.allocator), .llvm_type = struct_ty, }; } const UnionBuildResult = struct { variant_sx_types: []const Type, llvm_type: c.LLVMTypeRef, max_payload_size: u64, payload_field_index: c_uint, }; fn buildUnionFields(self: *CodeGen, name: []const u8, variant_type_nodes: []const ?*Node) !UnionBuildResult { var variant_sx_types = std.ArrayList(Type).empty; var max_payload_size: u64 = 0; for (variant_type_nodes) |vt| { if (vt) |type_node| { const sx_ty = self.resolveType(type_node); try variant_sx_types.append(self.allocator, sx_ty); const llvm_ty = self.typeToLLVM(sx_ty); const size = self.getTypeSize(llvm_ty); if (size > max_payload_size) max_payload_size = size; } else { try variant_sx_types.append(self.allocator, .void_type); } } const name_z = try self.allocator.dupeZ(u8, name); const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); const tag_ty = self.getEnumLLVMType(name); const i8_ty = self.i8Type(); const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); var union_fields = [2]c.LLVMTypeRef{ tag_ty, payload_array_ty }; c.LLVMStructSetBody(union_ty, &union_fields, 2, 0); return .{ .variant_sx_types = try variant_sx_types.toOwnedSlice(self.allocator), .llvm_type = union_ty, .max_payload_size = max_payload_size, .payload_field_index = 1, }; } fn hoistInlineTypeDecl(self: *CodeGen, parent_name: []const u8, child_name: []const u8, type_node: *Node) anyerror!void { const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ parent_name, child_name }); switch (type_node.data) { .struct_decl => |inline_sd| { var hoisted = inline_sd; hoisted.name = synthetic_name; try self.registerStructType(hoisted); type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; }, .union_decl => |inline_ud| { var hoisted_ud = inline_ud; hoisted_ud.name = synthetic_name; try self.registerUnionType(hoisted_ud); type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; }, .enum_decl => |inline_ed| { if (inline_ed.variant_types.len > 0) { // Tagged enum with payloads var hoisted = inline_ed; hoisted.name = synthetic_name; try self.registerTaggedEnum(hoisted); } else { try self.type_registry.put(synthetic_name, .{ .plain_enum = inline_ed.variant_names }); _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); if (inline_ed.backing_type) |bt_node| { const bt = self.resolveType(bt_node); try self.enum_backing_types.put(synthetic_name, self.typeToLLVM(bt)); } } type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; }, else => {}, } } fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { // Generic struct: store as template instead of registering now if (sd.type_params.len > 0) { try self.generic_struct_templates.put(sd.name, sd); return; } // Pre-pass: hoist inline type declarations from field types for (sd.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft); } const build = try self.buildStructFields(sd.name, sd.field_types); // Process field defaults: replace #run expressions with comptime global references var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); for (sd.field_defaults, 0..) |fd, i| { if (fd != null and fd.?.data == .comptime_expr) { const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i }); const field_type_override: ?Type = if (i < build.field_sx_types.len) build.field_sx_types[i] else null; try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override); const id_node = try self.allocator.create(Node); id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } }; resolved_defaults[i] = id_node; } else { resolved_defaults[i] = fd; } } const sinfo = StructInfo{ .field_names = sd.field_names, .field_types = build.field_sx_types, .field_defaults = resolved_defaults, .llvm_type = build.llvm_type, }; try self.type_registry.put(sd.name, .{ .struct_info = sinfo }); _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); } fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void { // Pre-pass: hoist inline type declarations from variant types for (ud.variant_types, 0..) |vt_opt, i| { if (vt_opt) |vt| { try self.hoistInlineTypeDecl(ud.name, ud.variant_names[i], vt); } } // Check if backing type is a struct layout specification const layout_info = try self.resolveEnumLayout(ud); if (layout_info) |layout| { // Struct-backed layout: use the struct's LLVM type directly try self.enum_backing_types.put(ud.name, layout.tag_llvm_type); // Resolve variant sx types var variant_sx_types = std.ArrayList(Type).empty; for (ud.variant_types) |vt| { if (vt) |type_node| { try variant_sx_types.append(self.allocator, self.resolveType(type_node)); } else { try variant_sx_types.append(self.allocator, .void_type); } } const tei_layout = TaggedEnumInfo{ .variant_names = ud.variant_names, .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), .llvm_type = layout.llvm_type, .max_payload_size = layout.payload_size, .payload_field_index = layout.payload_field_index, }; try self.type_registry.put(ud.name, .{ .tagged_enum = tei_layout }); } else { // Primitive backing type (e.g. enum u32 { ... }) if (ud.backing_type) |bt_node| { const bt = self.resolveType(bt_node); try self.enum_backing_types.put(ud.name, self.typeToLLVM(bt)); } const build = try self.buildUnionFields(ud.name, ud.variant_types); const tei_build = TaggedEnumInfo{ .variant_names = ud.variant_names, .variant_types = build.variant_sx_types, .llvm_type = build.llvm_type, .max_payload_size = build.max_payload_size, .payload_field_index = build.payload_field_index, }; try self.type_registry.put(ud.name, .{ .tagged_enum = tei_build }); } _ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name }); // Compute and store variant values (explicit or sequential) const values = try self.allocator.alloc(i64, ud.variant_names.len); for (ud.variant_names, 0..) |_, i| { if (ud.variant_values.len > i and ud.variant_values[i] != null) { const val_node = ud.variant_values[i].?; values[i] = switch (val_node.data) { .int_literal => |il| il.value, else => @as(i64, @intCast(i)), }; } else { values[i] = @intCast(i); } } try self.enum_variant_values.put(ud.name, values); } const EnumLayoutInfo = struct { llvm_type: c.LLVMTypeRef, tag_llvm_type: c.LLVMTypeRef, payload_field_index: c_uint, payload_size: u64, }; /// Resolve a struct-backed layout for a tagged enum. /// Returns null if the backing type is a primitive (e.g. u32), in which case /// the caller should fall back to buildUnionFields. /// /// The layout struct must have: /// - A field named `tag` (integer type) — the discriminant /// - A field named `payload` (array type) — the overlay area for variant data /// - Any other fields are treated as padding/reserved fn resolveEnumLayout(self: *CodeGen, ud: ast.EnumDecl) !?EnumLayoutInfo { const bt_node = ud.backing_type orelse return null; // Check for inline struct: enum struct { ... } { ... } if (bt_node.data == .struct_decl) { const layout_name = try std.fmt.allocPrint(self.allocator, "{s}.__layout", .{ud.name}); var sd = bt_node.data.struct_decl; sd.name = layout_name; try self.registerStructType(sd); return try self.validateEnumLayout(ud.name, layout_name); } // Check for named struct reference: enum MyLayout { ... } if (bt_node.data == .type_expr) { const name = bt_node.data.type_expr.name; // If it resolves to a primitive type, it's not a layout struct if (Type.fromName(name) != null) return null; // Check type aliases const resolved = self.resolveAlias(name); if (Type.fromName(resolved) != null) return null; // Must be a registered struct if (self.lookupStructInfo(resolved) != null) { return try self.validateEnumLayout(ud.name, resolved); } } return null; } fn validateEnumLayout(self: *CodeGen, enum_name: []const u8, layout_name: []const u8) !EnumLayoutInfo { const layout = self.lookupStructInfo(layout_name) orelse { return self.emitErrorFmt("enum '{s}': layout type '{s}' is not a registered struct", .{ enum_name, layout_name }); }; // Find 'tag' field var tag_index: ?usize = null; var payload_index: ?usize = null; for (layout.field_names, 0..) |fname, i| { if (std.mem.eql(u8, fname, "tag")) { tag_index = i; } else if (std.mem.eql(u8, fname, "payload")) { payload_index = i; } } if (tag_index == null) { return self.emitErrorFmt( "enum '{s}': layout struct '{s}' must have a field named 'tag' (the discriminant). Expected layout: struct {{ tag: ; payload: [N]; }}", .{ enum_name, layout_name }, ); } if (payload_index == null) { return self.emitErrorFmt( "enum '{s}': layout struct '{s}' must have a field named 'payload' (the variant data area). Expected layout: struct {{ tag: ; payload: [N]; }}", .{ enum_name, layout_name }, ); } const tag_ty = layout.field_types[tag_index.?]; const payload_ty = layout.field_types[payload_index.?]; // Validate tag is an integer type switch (tag_ty) { .signed, .unsigned => {}, else => return self.emitErrorFmt( "enum '{s}': layout field 'tag' must be an integer type (e.g. u32), got '{s}'", .{ enum_name, tag_ty.displayName(self.allocator) catch "?" }, ), } // Validate payload is an array type const payload_size = switch (payload_ty) { .array_type => |info| blk: { const elem_ty = Type.fromName(info.element_name) orelse { return self.emitErrorFmt( "enum '{s}': layout field 'payload' has unresolved element type '{s}'", .{ enum_name, info.element_name }, ); }; const elem_llvm = self.typeToLLVM(elem_ty); break :blk self.getTypeSize(elem_llvm) * info.length; }, else => return self.emitErrorFmt( "enum '{s}': layout field 'payload' must be an array type (e.g. [30]u32), got '{s}'", .{ enum_name, payload_ty.displayName(self.allocator) catch "?" }, ), }; return .{ .llvm_type = layout.llvm_type, .tag_llvm_type = self.typeToLLVM(tag_ty), .payload_field_index = @intCast(payload_index.?), .payload_size = payload_size, }; } fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { // Hoist inline type declarations from field types for (ud.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(ud.name, ud.field_names[i], ft); } // Compute max field size and resolve field types const data_layout = c.LLVMGetModuleDataLayout(self.module); var field_sx_types = std.ArrayList(Type).empty; var max_size: u64 = 0; for (ud.field_types) |ft| { const resolved = self.resolveType(ft); try field_sx_types.append(self.allocator, resolved); const llvm_ty = self.typeToLLVM(resolved); const size = c.LLVMABISizeOfType(data_layout, llvm_ty); if (size > max_size) max_size = size; } // LLVM type: byte array sized to the largest field const byte_ty = self.i8Type(); const llvm_type = c.LLVMArrayType(byte_ty, @intCast(max_size)); const resolved_field_types = try field_sx_types.toOwnedSlice(self.allocator); // Build promoted fields map from anonymous struct members var promoted = std.StringHashMap(PromotedField).init(self.allocator); for (ud.field_names, 0..) |_, i| { const fty = resolved_field_types[i]; if (fty.isStruct()) { // Check if this is an anonymous struct (name contains __anon_) const sname = fty.struct_type; if (std.mem.indexOf(u8, sname, ".__anon_") != null) { if (self.lookupStructInfo(sname)) |sinfo| { for (sinfo.field_names, 0..) |sf_name, sf_idx| { try promoted.put(sf_name, .{ .struct_name = sname, .field_index = sf_idx, .field_type = sinfo.field_types[sf_idx], }); } } } } } const uinfo = UnionInfo{ .field_names = ud.field_names, .field_types = resolved_field_types, .llvm_type = llvm_type, .total_size = max_size, .promoted_fields = promoted, }; try self.type_registry.put(ud.name, .{ .union_info = uinfo }); // Note: C-style unions are not registered with the Any type system. // They can't be meaningfully printed as a whole — access individual fields instead. } fn genTaggedEnumLiteral(self: *CodeGen, el: ast.EnumLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef { const uname = expected_union_name orelse (if (self.current_return_type.isUnion()) self.current_return_type.union_type else null) orelse return self.emitError("cannot infer enum type for literal"); const resolved_name = self.resolveAlias(uname); const info = try self.getTaggedEnumInfo(resolved_name); // Find variant index var variant_idx: ?u32 = null; for (info.variant_names, 0..) |vn, i| { if (std.mem.eql(u8, vn, el.name)) { variant_idx = @intCast(i); break; } } const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ el.name, resolved_name }); // Alloca union const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp"); const tag_ty = self.getEnumLLVMType(resolved_name); // Store tag (field 0) — use explicit value if available, otherwise index const tag_gep = self.structGEP(info.llvm_type, alloca, 0, "tag"); const tag_val: u64 = if (self.enum_variant_values.get(resolved_name)) |vals| @bitCast(vals[idx]) else idx; _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(tag_ty, tag_val, 0), tag_gep); // Store payload (field 1) if not void if (el.payload) |payload_node| { const variant_ty = info.variant_types[idx]; if (variant_ty != .void_type) { const payload_val = try self.genExprAsType(payload_node, variant_ty); self.storeStructField(info.llvm_type, alloca, info.payload_field_index, payload_val); } } return alloca; } fn genStructLiteral(self: *CodeGen, sl: ast.StructLiteral, expected_struct_name: ?[]const u8) anyerror!c.LLVMValueRef { const raw_name = sl.struct_name orelse blk: { if (sl.type_expr) |te| { const ty = self.resolveType(te); if (ty.isStruct()) break :blk ty.struct_type; } break :blk expected_struct_name orelse return self.emitError("cannot infer struct type for literal"); }; // Resolve type aliases (e.g. Vec3 -> Vec__3_f32) const sname = self.resolveAlias(raw_name); const info = try self.getStructInfo(sname); // Alloca the struct and default-init all fields (zero or declared defaults) const name_z = try self.allocator.dupeZ(u8, sname); const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr); try self.genStructDefaultInit(alloca, info); // Determine if this is named or positional mode var has_named = false; for (sl.field_inits) |fi| { if (fi.name != null) { has_named = true; break; } } if (has_named) { // Named/shorthand mode: map by field name for (sl.field_inits) |fi| { const fname = fi.name orelse { // Positional field mixed with named — treat as identifier shorthand if (fi.value.data == .identifier) { const idx = try self.findFieldIndex(info.field_names, fi.value.data.identifier.name, sname); const val = try self.genExprAsType(fi.value, info.field_types[idx]); self.storeStructField(info.llvm_type, alloca, @intCast(idx), val); continue; } return self.emitError("mixed positional and named fields in struct literal"); }; const idx = try self.findFieldIndex(info.field_names, fname, sname); const val = try self.genExprAsType(fi.value, info.field_types[idx]); self.storeStructField(info.llvm_type, alloca, @intCast(idx), val); } } else { // Positional mode: assign in order for (sl.field_inits, 0..) |fi, i| { if (i >= info.field_names.len) return self.emitErrorFmt("too many fields in struct literal (expected {d})", .{info.field_names.len}); const val = try self.genExprAsType(fi.value, info.field_types[i]); self.storeStructField(info.llvm_type, alloca, @intCast(i), val); } } return alloca; } /// Generate an array literal as an alloca with elements stored via GEP. /// If target_ty is provided, elements are converted to the array's element type. /// Otherwise, element type is inferred from the first element. fn genArrayLiteral(self: *CodeGen, al: ast.ArrayLiteral, target_ty_opt: ?Type) !c.LLVMValueRef { const arr_ty: Type = target_ty_opt orelse blk: { // Infer from first element if (al.elements.len == 0) return self.emitError("cannot infer type of empty array literal"); const elem_ty = self.inferType(al.elements[0]); const elem_name = try elem_ty.displayName(self.allocator); break :blk .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; }; const arr_info = arr_ty.array_type; const elem_sx_ty = try self.resolveElementType(arr_info.element_name, "array"); const llvm_arr_ty = self.typeToLLVM(arr_ty); const alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "arr"); const len = @min(al.elements.len, arr_info.length); for (0..len) |i| { const val = try self.genExprAsType(al.elements[i], elem_sx_ty); const gep = self.gepArrayElement(llvm_arr_ty, alloca, self.constInt32(@intCast(i)), "arr_elem"); _ = c.LLVMBuildStore(self.builder, val, gep); } return alloca; } fn genSliceLiteral(self: *CodeGen, al: ast.ArrayLiteral, slice_ty: Type) !c.LLVMValueRef { const elem_name = slice_ty.slice_type.element_name; const elem_sx_ty = try self.resolveElementType(elem_name, "slice"); const n: u32 = @intCast(al.elements.len); // Create backing array [N]elem on the stack const arr_ty: Type = .{ .array_type = .{ .element_name = elem_name, .length = n } }; const llvm_arr_ty = self.typeToLLVM(arr_ty); const arr_alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "slice_backing"); // Fill elements for (0..n) |i| { const val = try self.genExprAsType(al.elements[i], elem_sx_ty); const gep = self.gepArrayElement(llvm_arr_ty, arr_alloca, self.constInt32(@intCast(i)), "slice_elem"); _ = c.LLVMBuildStore(self.builder, val, gep); } // Build slice {ptr, len} const elem_ptr = self.arrayDecayToPointer(llvm_arr_ty, arr_alloca, "slice_data"); return self.buildFatPointer(self.getStringStructType(), elem_ptr, self.constInt64(n)); } fn genVectorLiteral(self: *CodeGen, al: ast.ArrayLiteral, vec_ty: Type) !c.LLVMValueRef { const vec_info = vec_ty.vector_type; const elem_sx_ty = try self.resolveElementType(vec_info.element_name, "vector"); const llvm_vec_ty = self.typeToLLVM(vec_ty); var vec_val = self.getUndef(llvm_vec_ty); const len = @min(al.elements.len, vec_info.length); for (0..len) |i| { const elem_val = try self.genExprAsType(al.elements[i], elem_sx_ty); const idx = self.constInt32(@intCast(i)); vec_val = c.LLVMBuildInsertElement(self.builder, vec_val, elem_val, idx, "vec_ins"); } return vec_val; } fn broadcastScalar(self: *CodeGen, scalar: c.LLVMValueRef, vec_ty: Type) c.LLVMValueRef { const vec_info = vec_ty.vector_type; const llvm_vec_ty = self.typeToLLVM(vec_ty); // Insert scalar at index 0 of undef vector var vec = self.getUndef(llvm_vec_ty); const zero = self.constInt32(0); vec = c.LLVMBuildInsertElement(self.builder, vec, scalar, zero, "splat_ins"); // Shuffle with zeroinitializer mask to broadcast element 0 to all lanes const mask_ty = c.LLVMVectorType(self.i32Type(), vec_info.length); const mask = c.LLVMConstNull(mask_ty); return c.LLVMBuildShuffleVector(self.builder, vec, self.getUndef(llvm_vec_ty), mask, "splat"); } fn genExprAsType(self: *CodeGen, node: *Node, target_ty: Type) !c.LLVMValueRef { self.current_span = node.span; // xx prefix: unwrap and convert freely (explicit cast) if (node.data == .unary_op and node.data.unary_op.op == .xx) { const inner = node.data.unary_op.operand; const val = try self.genExpr(inner); const src_ty = self.inferType(inner); return self.convertValue(val, src_ty, target_ty); } // Function pointer target: bypass narrowing check, just produce the pointer value if (target_ty.isFunctionType()) { return try self.genExpr(node); } // String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping) if (node.data == .string_literal and target_ty.isPointer()) { const lit = node.data.string_literal; const content = if (lit.is_raw) lit.raw else try unescape.unescapeString(self.allocator, lit.raw); const str_z = try self.allocator.dupeZ(u8, content); return self.buildGlobalString(str_z.ptr, "str"); } // Enum literal assigned to enum type: resolve variant value if (node.data == .enum_literal and target_ty.isEnum()) { return self.genEnumLiteral(node.data.enum_literal.name, target_ty.enum_type); } // Bitwise op on enum type: recursively generate both sides with enum context if (node.data == .binary_op and (node.data.binary_op.op == .bit_or or node.data.binary_op.op == .bit_and) and target_ty.isEnum()) { const binop = node.data.binary_op; const lhs = try self.genExprAsType(binop.lhs, target_ty); const rhs = try self.genExprAsType(binop.rhs, target_ty); const b = self.builder; return if (binop.op == .bit_or) c.LLVMBuildOr(b, lhs, rhs, "bortmp") else c.LLVMBuildAnd(b, lhs, rhs, "bandtmp"); } // Enum/union literal assigned to union type: construct tagged enum if (node.data == .enum_literal and target_ty.isUnion()) { const el = node.data.enum_literal; return self.genTaggedEnumLiteral(el, target_ty.union_type); } // Struct literal targeting union type: .Variant.{fields} pattern // Parsed as struct_literal with type_expr = enum_literal("Variant") if (node.data == .struct_literal and target_ty.isUnion()) { const sl = node.data.struct_literal; if (sl.struct_name == null) { if (sl.type_expr) |te| { if (te.data == .enum_literal) { const variant_name = te.data.enum_literal.name; const uname = self.resolveAlias(target_ty.union_type); const info = try self.getTaggedEnumInfo(uname); // Find variant index var variant_idx: ?u32 = null; for (info.variant_names, 0..) |vn, vi| { if (std.mem.eql(u8, vn, variant_name)) { variant_idx = @intCast(vi); break; } } const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ variant_name, uname }); const variant_ty = info.variant_types[idx]; // Alloca union, store tag const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_lit"); const tag_llvm_ty = self.getEnumLLVMType(uname); self.storeStructField(info.llvm_type, alloca, 0, c.LLVMConstInt(tag_llvm_ty, idx, 0)); // Store struct payload if (variant_ty != .void_type) { const payload_struct_name = if (variant_ty.isStruct()) variant_ty.struct_type else null; const payload_alloca = try self.genStructLiteral(.{ .struct_name = payload_struct_name, .type_expr = null, .field_inits = sl.field_inits, }, payload_struct_name); const payload_gep = self.structGEP(info.llvm_type, alloca, info.payload_field_index, "payload"); const payload_llvm_ty = self.typeToLLVM(variant_ty); const struct_val = c.LLVMBuildLoad2(self.builder, payload_llvm_ty, payload_alloca, "struct_load"); _ = c.LLVMBuildStore(self.builder, struct_val, payload_gep); } return alloca; } } } } // Struct literal targeting struct type: pass struct name context if (node.data == .struct_literal and target_ty.isStruct()) { const alloca = try self.genStructLiteral(node.data.struct_literal, target_ty.struct_type); // genStructLiteral returns an alloca pointer — load the value for by-value passing const sname = self.resolveAlias(target_ty.struct_type); if (self.lookupStructInfo(sname)) |si| { return c.LLVMBuildLoad2(self.builder, si.llvm_type, alloca, "struct_val"); } return alloca; } // Array literal with target array type: generate with element conversion if (node.data == .array_literal and target_ty.isArray()) { return self.genArrayLiteral(node.data.array_literal, target_ty); } // Array literal with target vector type: build via undef + InsertElement if (node.data == .array_literal and target_ty.isVector()) { return self.genVectorLiteral(node.data.array_literal, target_ty); } // Array literal with target slice type: build stack-backed slice if (node.data == .array_literal and target_ty.isSlice()) { return self.genSliceLiteral(node.data.array_literal, target_ty); } // Infer source type once for all coercion checks below const src_ty = self.inferType(node); // Array to slice coercion: [N]T → []T if (target_ty.isSlice() and src_ty.isArray()) { const arr_info = src_ty.array_type; // Get the alloca pointer for the array (not the loaded value) const arr_alloca = blk: { if (node.data == .identifier) { if (self.named_values.get(node.data.identifier.name)) |entry| { break :blk entry.ptr; } } if (node.data == .field_access) { break :blk try self.genAddressOf(node); } // Fallback: generate the expression and hope it returns a pointer break :blk try self.genExpr(node); }; // GEP to get pointer to first element const elem_ptr = self.arrayDecayToPointer(self.typeToLLVM(src_ty), arr_alloca, "arr_data"); // Build slice struct {ptr, len} return self.buildFatPointer(self.getStringStructType(), elem_ptr, self.constInt64(arr_info.length)); } // Array to many-pointer coercion: [N]T → [*]T if (target_ty.isManyPointer() and src_ty.isArray()) { const arr_info = src_ty.array_type; if (std.mem.eql(u8, arr_info.element_name, target_ty.many_pointer_type.element_name)) { const arr_alloca = blk: { if (node.data == .identifier) { if (self.named_values.get(node.data.identifier.name)) |entry| { break :blk entry.ptr; } } if (node.data == .field_access) { break :blk try self.genAddressOf(node); } break :blk try self.genExpr(node); }; return self.arrayDecayToPointer(self.typeToLLVM(src_ty), arr_alloca, "arr_decay"); } } // Slice to many-pointer coercion: []T → [*]T (extract .ptr from fat pointer) if (target_ty.isManyPointer() and src_ty.isSlice()) { if (std.mem.eql(u8, src_ty.slice_type.element_name, target_ty.many_pointer_type.element_name)) { const slice_val = try self.genExpr(node); return self.extractValue(slice_val, 0, "slice_decay"); } } // Implicit address-of: passing T where *T is expected → auto & if (target_ty.isPointer()) { const pointee_name = target_ty.pointer_type.pointee_name; const src_matches = if (src_ty.isStruct()) std.mem.eql(u8, src_ty.struct_type, pointee_name) or (if (self.lookupAlias(src_ty.struct_type)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or (if (self.lookupAlias(pointee_name)) |alias| std.mem.eql(u8, alias, src_ty.struct_type) else false) else if (src_ty.isUnion()) blk: { const uname = src_ty.union_type; break :blk std.mem.eql(u8, uname, pointee_name) or (if (self.lookupAlias(uname)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or (if (self.lookupAlias(pointee_name)) |alias| std.mem.eql(u8, alias, uname) else false); } else if (Type.fromName(pointee_name)) |pointee_ty| src_ty.eql(pointee_ty) else false; if (src_matches) { if (node.data == .identifier) { if (self.named_values.get(node.data.identifier.name)) |entry| { return entry.ptr; } } } } var val = try self.genExpr(node); // Struct literals return alloca pointers — load the value for by-value passing if (src_ty.isStruct() and target_ty.isStruct()) { if (c.LLVMGetTypeKind(c.LLVMTypeOf(val)) == c.LLVMPointerTypeKind) { const info = self.lookupStructInfo(src_ty.struct_type) orelse self.lookupStructInfo(self.resolveAlias(src_ty.struct_type)); if (info) |si| { val = c.LLVMBuildLoad2(self.builder, si.llvm_type, val, "struct_load"); } } } // Scalar to vector broadcast if (target_ty.isVector() and !src_ty.isVector()) { const elem_ty = target_ty.vectorElementType() orelse return self.emitError("cannot determine vector element type"); const converted = self.convertValue(val, src_ty, elem_ty); return self.broadcastScalar(converted, target_ty); } // Literals are exempt from narrowing checks if (node.data == .int_literal or node.data == .float_literal) { return self.convertValue(val, src_ty, target_ty); } // Check for narrowing conversion if (!src_ty.isImplicitlyConvertibleTo(target_ty)) { // Narrowing without xx — compile error return self.emitErrorFmt("narrowing conversion from '{s}' to '{s}' requires explicit 'xx' cast", .{ src_ty.displayName(self.allocator) catch "?", target_ty.displayName(self.allocator) catch "?", }); } return self.convertValue(val, src_ty, target_ty); } /// Convert an LLVM value from src_ty to target_ty, emitting appropriate casts. fn convertValue(self: *CodeGen, val: c.LLVMValueRef, src_ty: Type, target_ty: Type) c.LLVMValueRef { // Same type → return as-is if (src_ty.eql(target_ty)) return val; // string <-> []u8: identical LLVM type {ptr, i64}, no conversion needed if ((src_ty == .string_type and target_ty.isSlice() and std.mem.eql(u8, target_ty.slice_type.element_name, "u8")) or (src_ty.isSlice() and std.mem.eql(u8, src_ty.slice_type.element_name, "u8") and target_ty == .string_type)) return val; const target_llvm = self.typeToLLVM(target_ty); // Any → concrete type: extract the i64 value and convert if (src_ty.isAny()) { const i64_val = self.extractValue(val, 1, "any_extract"); if (target_ty.isInt()) { if (target_ty.bitWidth() < 64) { return self.trunc(i64_val, target_llvm, "any_to_int"); } return i64_val; } if (target_ty == .boolean) { return self.trunc(i64_val, self.i1Type(), "any_to_bool"); } if (target_ty == .f64) { return self.bitCast(i64_val, self.f64Type(), "any_to_f64"); } if (target_ty == .f32) { const as_f64 = self.bitCast(i64_val, self.f64Type(), "any_f64_tmp"); return c.LLVMBuildFPTrunc(self.builder, as_f64, self.f32Type(), "any_to_f32"); } if (target_ty == .string_type) { // i64 is a pointer to {ptr, i32} on the stack return self.loadFromI64Ptr(i64_val, self.getStringStructType(), "any_to_str"); } if (target_ty.isStruct()) { const sname = target_ty.struct_type; if (self.lookupStructInfo(sname)) |info| { return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_struct"); } } if (target_ty.isEnum()) { const enum_llvm_ty = self.getEnumLLVMType(target_ty.enum_type); const enum_bits = c.LLVMGetIntTypeWidth(enum_llvm_ty); if (enum_bits < 64) return self.trunc(i64_val, enum_llvm_ty, "any_to_enum"); return i64_val; } if (target_ty.isUnion()) { const uname = target_ty.union_type; if (self.lookupTaggedEnumInfo(uname)) |info| { return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_union"); } } if (target_ty.isPointer() or target_ty.isManyPointer()) { return self.intToPtr(i64_val, "any_to_ptr"); } return i64_val; } // Float → float conversions if (src_ty.isFloat() and target_ty.isFloat()) { if (target_ty.bitWidth() > src_ty.bitWidth()) { return c.LLVMBuildFPExt(self.builder, val, target_llvm, "fext"); } else { return c.LLVMBuildFPTrunc(self.builder, val, target_llvm, "ftrunc"); } } // Int → float conversions if (src_ty.isInt() and target_ty.isFloat()) { if (src_ty.isSigned()) { return c.LLVMBuildSIToFP(self.builder, val, target_llvm, "sitofp"); } else { return c.LLVMBuildUIToFP(self.builder, val, target_llvm, "uitofp"); } } // Float → int conversions if (src_ty.isFloat() and target_ty.isInt()) { if (target_ty.isSigned()) { return c.LLVMBuildFPToSI(self.builder, val, target_llvm, "fptosi"); } else { return c.LLVMBuildFPToUI(self.builder, val, target_llvm, "fptoui"); } } // Pointer → int: PtrToInt if ((src_ty.isPointer() or src_ty.isManyPointer()) and target_ty.isInt()) { const as_i64 = self.ptrToInt(val, "ptrtoint"); if (target_ty.bitWidth() < 64) { return self.trunc(as_i64, target_llvm, "ptr_trunc"); } return as_i64; } // Union → int: extract the tag field (index 0) if (src_ty.isUnion() and target_ty.isInt()) { const uname = src_ty.union_type; if (self.lookupTaggedEnumInfo(uname)) |info| { const tag_llvm_ty = self.getEnumLLVMType(uname); const tag_bits = c.LLVMGetIntTypeWidth(tag_llvm_ty); const tmp = self.buildEntryBlockAlloca(info.llvm_type, "union_cast"); _ = c.LLVMBuildStore(self.builder, val, tmp); const tag_val = self.loadStructField(info.llvm_type, tmp, 0, tag_llvm_ty); if (target_ty.bitWidth() == tag_bits) return tag_val; if (target_ty.bitWidth() > tag_bits) return self.sExt(tag_val, target_llvm, "tag_ext"); return self.trunc(tag_val, target_llvm, "tag_trunc"); } } // Int → int conversions if (src_ty.isInt() and target_ty.isInt()) { const sw = src_ty.bitWidth(); const tw = target_ty.bitWidth(); if (tw > sw) { // Extend — use SExt if source is signed, ZExt if unsigned if (src_ty.isSigned()) { return self.sExt(val, target_llvm, "sext"); } else { return self.zExt(val, target_llvm, "zext"); } } else if (tw < sw) { // Truncate return self.trunc(val, target_llvm, "trunc"); } // Same width, different signedness — no-op (bit pattern is the same) return val; } // Int → pointer/function_type: IntToPtr (for xx cast from integer to pointer) if (src_ty.isInt() and (target_ty.isPointer() or target_ty.isManyPointer() or target_ty.isFunctionType())) { return self.intToPtr(val, "inttoptr"); } // Slice/string → pointer: extract .ptr from fat pointer if ((src_ty.isSlice() or src_ty == .string_type) and (target_ty.isPointer() or target_ty.isManyPointer())) { return self.extractValue(val, 0, "slice_to_ptr"); } // Enum → int: extend or truncate from backing type to target int if (src_ty.isEnum() and target_ty.isInt()) { const enum_bits = c.LLVMGetIntTypeWidth(self.getEnumLLVMType(src_ty.enum_type)); const target_bits = target_ty.bitWidth(); if (target_bits > enum_bits) { return self.zExt(val, target_llvm, "enum_to_int"); } else if (target_bits < enum_bits) { return self.trunc(val, target_llvm, "enum_to_int"); } return val; } // Int → enum: extend or truncate from source int to backing type if (src_ty.isInt() and target_ty.isEnum()) { const enum_llvm_ty = self.getEnumLLVMType(target_ty.enum_type); const enum_bits = c.LLVMGetIntTypeWidth(enum_llvm_ty); const src_bits = src_ty.bitWidth(); if (enum_bits > src_bits) { return self.zExt(val, enum_llvm_ty, "int_to_enum"); } else if (enum_bits < src_bits) { return self.trunc(val, enum_llvm_ty, "int_to_enum"); } return val; } // *[N]T → [*]T: pointer to array decays to many-pointer (both opaque ptrs, no-op) if (src_ty.isPointer() and target_ty.isManyPointer()) { return val; } // Pointer → function_type or function_type → pointer: both are opaque pointers, no-op if ((src_ty.isPointer() or src_ty.isManyPointer()) and target_ty.isFunctionType()) { return val; } if (src_ty.isFunctionType() and (target_ty.isPointer() or target_ty.isManyPointer())) { return val; } return val; } fn findNameIndex(_: *CodeGen, names: []const []const u8, name: []const u8) ?usize { for (names, 0..) |n, i| { if (std.mem.eql(u8, n, name)) return i; } return null; } fn findFieldIndex(self: *CodeGen, field_names: []const []const u8, field: []const u8, struct_name: []const u8) !usize { return self.findNameIndex(field_names, field) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ field, struct_name }); } fn componentToIndex(ch: u8) ?u32 { return switch (ch) { 'x', 'r', 'u' => 0, 'y', 'g', 'v' => 1, 'z', 'b' => 2, 'w', 'a' => 3, else => null, }; } fn genMathIntrinsic(self: *CodeGen, call_node: ast.Call, comptime name: []const u8) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError(name ++ " expects exactly 1 argument"); const arg_val = try self.genExpr(call_node.args[0]); const arg_ty = self.inferType(call_node.args[0]); const is_f64 = std.meta.eql(arg_ty, Type.f64); const intrinsic_name: [*c]const u8 = if (is_f64) "llvm." ++ name ++ ".f64" else "llvm." ++ name ++ ".f32"; const llvm_float_ty = if (is_f64) self.f64Type() else self.f32Type(); var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name); if (intrinsic_fn == null) { var param_types = [_]c.LLVMTypeRef{llvm_float_ty}; const fn_type = c.LLVMFunctionType(llvm_float_ty, ¶m_types, 1, 0); intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type); } var args = [_]c.LLVMValueRef{arg_val}; return c.LLVMBuildCall2(self.builder, c.LLVMGlobalGetValueType(intrinsic_fn.?), intrinsic_fn.?, &args, 1, name.ptr); } fn genSizeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("size_of expects exactly 1 argument"); const arg = call_node.args[0]; const ty = self.resolveType(arg); if (std.meta.eql(ty, Type.void_type)) { // Uninstantiated generic type function → size of a function pointer if (arg.data == .identifier or arg.data == .type_expr) { const name = if (arg.data == .identifier) arg.data.identifier.name else arg.data.type_expr.name; if (self.generic_templates.contains(name) or self.generic_struct_templates.contains(name)) { return self.constInt64(self.getTypeSize(self.ptrType())); } } return self.constInt64(0); } const llvm_ty = self.typeToLLVM(ty); const size = self.getTypeSize(llvm_ty); return self.constInt64(size); } fn genTypeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("type_of expects exactly 1 argument"); const arg = call_node.args[0]; const arg_ty = self.inferType(arg); const i64_ty = self.i64Type(); // For Any values: extract the runtime tag (field 0) if (arg_ty.isAny()) { const val = try self.genExpr(arg); return self.extractValue(val, 0, "type_of"); } // For known types: return the constant tag value const tag: u64 = switch (arg_ty) { .void_type => ANY_TAG_VOID, .boolean => ANY_TAG_BOOL, .signed => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, .unsigned => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, .f32 => ANY_TAG_F32, .f64 => ANY_TAG_F64, .string_type => ANY_TAG_STRING, .struct_type => |name| try self.getAnyTypeId(name, arg_ty), .enum_type => |name| try self.getAnyTypeId(name, arg_ty), .union_type => |name| try self.getAnyTypeId(name, arg_ty), .meta_type => ANY_TAG_TYPE, else => ANY_TAG_S32, }; return c.LLVMConstInt(i64_ty, tag, 0); } fn genTypeName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("type_name expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); const name = try ty.displayName(self.allocator); return self.buildConstStr(name); } fn genFieldCount(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("field_count expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); const i64_ty = self.i64Type(); if (ty.isStruct()) { const info = try self.getStructInfo(ty.struct_type); return c.LLVMConstInt(i64_ty, info.field_names.len, 0); } if (ty.isEnum()) { const variants = self.lookupEnumVariants(ty.enum_type) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); return c.LLVMConstInt(i64_ty, variants.len, 0); } if (ty.isVector()) { return c.LLVMConstInt(i64_ty, ty.vector_type.length, 0); } if (ty.isUnion()) { const info = try self.getTaggedEnumInfo(ty.union_type); return c.LLVMConstInt(i64_ty, info.variant_names.len, 0); } if (ty.isArray()) { return c.LLVMConstInt(i64_ty, ty.array_type.length, 0); } return self.emitError("field_count requires a struct, enum, vector, or array type"); } fn genFieldName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 2) return self.emitError("field_name expects 2 arguments: field_name(T, idx)"); const ty = self.resolveType(call_node.args[0]); // Get the name list and type key const names: []const []const u8, const type_key: []const u8 = if (ty.isStruct()) blk: { const info = try self.getStructInfo(ty.struct_type); break :blk .{ info.field_names, ty.struct_type }; } else if (ty.isEnum()) blk: { const variants = self.lookupEnumVariants(ty.enum_type) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); break :blk .{ variants, ty.enum_type }; } else if (ty.isUnion()) blk: { const info = try self.getTaggedEnumInfo(ty.union_type); break :blk .{ info.variant_names, ty.union_type }; } else return self.emitError("field_name requires a struct or enum type"); // Build a global array of string slices const n = names.len; const str_ty = self.getStringStructType(); const arr_ty = c.LLVMArrayType2(str_ty, n); const vals = try self.allocator.alloc(c.LLVMValueRef, n); for (names, 0..) |name, i| { vals[i] = self.buildConstStrGlobal(name); } const arr_init = c.LLVMConstArray2(str_ty, vals.ptr, @intCast(n)); const global_name = try self.allocator.dupeZ(u8, try std.fmt.allocPrint(self.allocator, "field_names.{s}", .{type_key})); var global = c.LLVMGetNamedGlobal(self.module, global_name.ptr); if (global == null) { global = c.LLVMAddGlobal(self.module, arr_ty, global_name.ptr); c.LLVMSetInitializer(global, arr_init); c.LLVMSetGlobalConstant(global, 1); c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); } // GEP into the array with runtime index const idx = try self.genExpr(call_node.args[1]); const elem_ptr = self.gepArrayElement(arr_ty, global, idx, "field_name_ptr"); return c.LLVMBuildLoad2(self.builder, str_ty, elem_ptr, "field_name"); } fn genFieldValue(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 2) return self.emitError("field_value expects 2 arguments: field_value(s, idx)"); const val = try self.genExpr(call_node.args[0]); const val_ty = self.inferType(call_node.args[0]); // Vector: extractelement + box as Any if (val_ty.isVector()) { const info = val_ty.vector_type; const elem_ty = Type.fromName(info.element_name) orelse return self.emitErrorFmt("unknown vector element type '{s}'", .{info.element_name}); const idx = try self.genExpr(call_node.args[1]); const elem = c.LLVMBuildExtractElement(self.builder, val, idx, "vec_elem"); return self.buildAnyValue(elem, elem_ty); } // Payload-less enum: return void Any (no payload to extract) if (val_ty.isEnum() and !val_ty.isUnion()) { return self.buildAnyValue(self.constInt64(0), .void_type); } // Tagged enum (with payloads): switch over tag, extract payload with correct type if (val_ty.isUnion()) { const uinfo = try self.getTaggedEnumInfo(val_ty.union_type); const union_alloca = self.buildEntryBlockAlloca(uinfo.llvm_type, "fv_union"); _ = c.LLVMBuildStore(self.builder, val, union_alloca); // Read tag (field 0) const tag_val = self.loadStructField(uinfo.llvm_type, union_alloca, 0, self.getEnumLLVMType(val_ty.union_type)); const payload_ptr = self.structGEP(uinfo.llvm_type, union_alloca, uinfo.payload_field_index, "fv_payload_ptr"); const n = uinfo.variant_names.len; const any_ty = self.getAnyStructType(); const sb = self.buildSwitch(tag_val, @intCast(n), "fv_merge", "fv_default"); var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; const tag_llvm_ty = self.getEnumLLVMType(val_ty.union_type); for (uinfo.variant_types, 0..) |vty, vi| { const case_bb = self.appendBB("fv_ucase"); c.LLVMAddCase(sb.sw, c.LLVMConstInt(tag_llvm_ty, @intCast(vi), 0), case_bb); self.positionAt(case_bb); const any_val = if (vty == .void_type) blk: { // Void variant: return Any with void tag const undef = self.getUndef(any_ty); const void_tag = self.constInt64(ANY_TAG_VOID); const with_tag = self.insertValue(undef, void_tag, 0, "void_tag"); const zero_val = self.constInt64(0); break :blk self.insertValue(with_tag, zero_val, 1, "void_any"); } else blk: { const payload = self.loadTyped(vty, payload_ptr, "fv_payload"); break :blk try self.buildAnyValue(payload, vty); }; try self.addPhiCase(&phi_vals, &phi_bbs, any_val, sb.merge_bb); } // Default: undef self.positionAt(sb.default_bb); try self.addPhiCase(&phi_vals, &phi_bbs, self.getUndef(any_ty), sb.merge_bb); self.positionAt(sb.merge_bb); const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, any_ty, "fv_uresult"); return phi; } // Slice: extract ptr, GEP to element, load, box as Any if (val_ty.isSlice()) { const sinfo = val_ty.slice_type; const elem_ty = Type.fromName(sinfo.element_name) orelse return self.emitErrorFmt("unknown slice element type '{s}'", .{sinfo.element_name}); const elem_llvm_ty = self.typeToLLVM(elem_ty); // val is {ptr, i32} — extract ptr const data_ptr = self.extractValue(val, 0, "fv_sdata"); const idx = try self.genExpr(call_node.args[1]); const elem_ptr = self.gepPointerElement(elem_llvm_ty, data_ptr, idx, "fv_selem"); const elem = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, elem_ptr, "fv_seval"); return self.buildAnyValue(elem, elem_ty); } // Array: GEP + load + box as Any if (val_ty.isArray()) { const ainfo = val_ty.array_type; const elem_ty = Type.fromName(ainfo.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{ainfo.element_name}); const arr_llvm_ty = self.typeToLLVM(val_ty); const elem_llvm_ty = self.typeToLLVM(elem_ty); const arr_alloca = self.buildEntryBlockAlloca(arr_llvm_ty, "fv_arr"); _ = c.LLVMBuildStore(self.builder, val, arr_alloca); const idx = try self.genExpr(call_node.args[1]); var gep_indices = [_]c.LLVMValueRef{ self.constInt32(0), idx, }; const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, arr_alloca, &gep_indices, 2, "fv_aelem"); const elem = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, elem_ptr, "fv_aeval"); return self.buildAnyValue(elem, elem_ty); } // Struct: switch over field indices const struct_val = val; const struct_ty = val_ty; if (!struct_ty.isStruct()) return self.emitError("field_value requires a struct, vector, enum, or array value"); const info = try self.getStructInfo(struct_ty.struct_type); const idx = try self.genExpr(call_node.args[1]); const n = info.field_names.len; // Store struct to alloca BEFORE the switch (switch is a terminator) const struct_alloca = self.buildEntryBlockAlloca(info.llvm_type, "fv_struct"); _ = c.LLVMBuildStore(self.builder, struct_val, struct_alloca); // Generate switch on idx with N cases const sb = self.buildSwitch(idx, @intCast(n), "fv_merge", "fv_default"); const any_ty = self.getAnyStructType(); var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; for (0..n) |i| { const case_bb = self.appendBB("fv_case"); const case_val = self.constInt64(i); c.LLVMAddCase(sb.sw, case_val, case_bb); self.positionAt(case_bb); // Extract field i via GEP + load const field_ptr = self.structGEP(info.llvm_type, struct_alloca, @intCast(i), "fv_field_ptr"); const field_llvm_ty = c.LLVMStructGetTypeAtIndex(info.llvm_type, @intCast(i)); const field_val = c.LLVMBuildLoad2(self.builder, field_llvm_ty, field_ptr, "fv_field"); const any_val = try self.buildAnyValue(field_val, info.field_types[i]); try self.addPhiCase(&phi_vals, &phi_bbs, any_val, sb.merge_bb); } // Default: return undef Any self.positionAt(sb.default_bb); try self.addPhiCase(&phi_vals, &phi_bbs, self.getUndef(any_ty), sb.merge_bb); self.positionAt(sb.merge_bb); const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, any_ty, "fv_result"); return phi; } fn genIsFlags(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("is_flags expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); const i1_type = self.i1Type(); if (ty.isEnum()) { const is_flags = self.flags_enum_types.contains(ty.enum_type); return c.LLVMConstInt(i1_type, @intFromBool(is_flags), 0); } return c.LLVMConstInt(i1_type, 0, 0); } fn genFieldValueInt(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 2) return self.emitError("field_value_int expects 2 arguments: field_value_int(T, idx)"); const ty = self.resolveType(call_node.args[0]); const i64_type = self.i64Type(); // For non-enum types (e.g. tagged enums compiled via dead code), return the index as value if (!ty.isEnum()) { return try self.genExpr(call_node.args[1]); } const enum_name = ty.enum_type; const values = self.enum_variant_values.get(enum_name); const variants = self.lookupEnumVariants(enum_name) orelse return try self.genExpr(call_node.args[1]); const n = variants.len; const idx = try self.genExpr(call_node.args[1]); const sb = self.buildSwitch(idx, @intCast(n), "fvi_merge", "fvi_default"); var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; for (0..n) |i| { const case_bb = self.appendBB("fvi_case"); c.LLVMAddCase(sb.sw, c.LLVMConstInt(i64_type, i, 0), case_bb); self.positionAt(case_bb); const val: u64 = if (values) |vals| @bitCast(vals[i]) else i; try self.addPhiCase(&phi_vals, &phi_bbs, c.LLVMConstInt(i64_type, val, 0), sb.merge_bb); } self.positionAt(sb.default_bb); try self.addPhiCase(&phi_vals, &phi_bbs, c.LLVMConstInt(i64_type, 0, 0), sb.merge_bb); self.positionAt(sb.merge_bb); const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, i64_type, "fvi_result"); return phi; } fn genFieldIndex(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 2) return self.emitError("field_index expects 2 arguments: field_index(T, value)"); const ty = self.resolveType(call_node.args[0]); const i64_type = self.i64Type(); // Handle tagged enums (union_type) — extract tag from field 0 if (ty == .union_type) { const union_name = ty.union_type; const info = self.lookupTaggedEnumInfo(union_name) orelse { _ = try self.genExpr(call_node.args[1]); return c.LLVMConstInt(i64_type, 0, 0); }; const values = self.enum_variant_values.get(union_name); const n = info.variant_names.len; const val = try self.genExpr(call_node.args[1]); // Extract tag from field 0 of the { tag, payload } struct const tag_val = self.extractValue(val, 0, "fi_tag"); const enum_llvm_ty = self.getEnumLLVMType(union_name); const sw_val = if (c.LLVMTypeOf(tag_val) != enum_llvm_ty) c.LLVMBuildIntCast2(self.builder, tag_val, enum_llvm_ty, 0, "fi_cast") else tag_val; const sb = self.buildSwitch(sw_val, @intCast(n), "fi_merge", "fi_default"); var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; var seen_values = std.ArrayList(u64).empty; for (0..n) |i| { const explicit_val: u64 = if (values) |vals| @bitCast(vals[i]) else i; var is_dup = false; for (seen_values.items) |sv| { if (sv == explicit_val) { is_dup = true; break; } } if (is_dup) continue; try seen_values.append(self.allocator, explicit_val); const case_bb = self.appendBB("fi_case"); c.LLVMAddCase(sb.sw, c.LLVMConstInt(enum_llvm_ty, explicit_val, 0), case_bb); self.positionAt(case_bb); try self.addPhiCase(&phi_vals, &phi_bbs, c.LLVMConstInt(i64_type, i, 0), sb.merge_bb); } self.positionAt(sb.default_bb); const neg_one = c.LLVMConstInt(i64_type, @bitCast(@as(i64, -1)), 0); try self.addPhiCase(&phi_vals, &phi_bbs, neg_one, sb.merge_bb); self.positionAt(sb.merge_bb); return try self.buildPhiNode(&phi_vals, &phi_bbs, i64_type, "fi_result"); } if (!ty.isEnum()) { _ = try self.genExpr(call_node.args[1]); return c.LLVMConstInt(i64_type, 0, 0); } const enum_name = ty.enum_type; // Flags enums don't use sequential indices if (self.flags_enum_types.contains(enum_name)) { _ = try self.genExpr(call_node.args[1]); return c.LLVMConstInt(i64_type, 0, 0); } const values = self.enum_variant_values.get(enum_name); const variants = self.lookupEnumVariants(enum_name) orelse return try self.genExpr(call_node.args[1]); const n = variants.len; const val = try self.genExpr(call_node.args[1]); // Ensure the switch value uses the enum's backing type const enum_llvm_ty = self.getEnumLLVMType(enum_name); const sw_val = if (c.LLVMTypeOf(val) != enum_llvm_ty) c.LLVMBuildIntCast2(self.builder, val, enum_llvm_ty, 0, "fi_cast") else val; const sb = self.buildSwitch(sw_val, @intCast(n), "fi_merge", "fi_default"); var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; var seen_values = std.ArrayList(u64).empty; for (0..n) |i| { const explicit_val: u64 = if (values) |vals| @bitCast(vals[i]) else i; // Skip duplicate values (first one wins) var is_dup = false; for (seen_values.items) |sv| { if (sv == explicit_val) { is_dup = true; break; } } if (is_dup) continue; try seen_values.append(self.allocator, explicit_val); const case_bb = self.appendBB("fi_case"); c.LLVMAddCase(sb.sw, c.LLVMConstInt(enum_llvm_ty, explicit_val, 0), case_bb); self.positionAt(case_bb); try self.addPhiCase(&phi_vals, &phi_bbs, c.LLVMConstInt(i64_type, i, 0), sb.merge_bb); } self.positionAt(sb.default_bb); const neg_one = c.LLVMConstInt(i64_type, @bitCast(@as(i64, -1)), 0); try self.addPhiCase(&phi_vals, &phi_bbs, neg_one, sb.merge_bb); self.positionAt(sb.merge_bb); const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, i64_type, "fi_result"); return phi; } fn genCast(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 2) return self.emitError("cast expects: cast(Type) expr"); const target_ty = self.resolveType(call_node.args[0]); const src_ty = self.inferType(call_node.args[1]); const val = try self.genExpr(call_node.args[1]); return self.convertValue(val, src_ty, target_ty); } fn genAlloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { if (args.len != 1) return self.emitError("alloc expects exactly 1 argument: alloc(size)"); const builtins = try self.requireBuiltins(); const size_val = try self.genExpr(args[0]); const i64_type = self.i64Type(); // calloc(size + 1, 1) — extra byte for null terminator const one_i64 = c.LLVMConstInt(i64_type, 1, 0); const size_plus_one = c.LLVMBuildAdd(self.builder, size_val, one_i64, "szp1"); const calloc_fn = builtins.calloc_fn; const calloc_ty = c.LLVMGlobalGetValueType(calloc_fn); var calloc_args = [_]c.LLVMValueRef{ size_plus_one, one_i64 }; const ptr = c.LLVMBuildCall2(self.builder, calloc_ty, calloc_fn, &calloc_args, 2, "alloc_ptr"); // Build string slice: {ptr, size} return self.buildStringSlice(ptr, size_val); } fn genMalloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { if (args.len != 1) return self.emitError("malloc expects exactly 1 argument: malloc(size)"); const builtins = try self.requireBuiltins(); const size_val = try self.genExpr(args[0]); const fn_ty = c.LLVMGlobalGetValueType(builtins.malloc_fn); var call_args = [_]c.LLVMValueRef{size_val}; return c.LLVMBuildCall2(self.builder, fn_ty, builtins.malloc_fn, &call_args, 1, "malloc_ptr"); } fn genFree(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { if (args.len != 1) return self.emitError("free expects exactly 1 argument: free(ptr)"); const builtins = try self.requireBuiltins(); const ptr_val = try self.genExpr(args[0]); const fn_ty = c.LLVMGlobalGetValueType(builtins.free_fn); var call_args = [_]c.LLVMValueRef{ptr_val}; _ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.free_fn, &call_args, 1, ""); return null; } fn genMemcpy(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { if (args.len != 3) return self.emitError("memcpy expects 3 arguments: memcpy(dst, src, size)"); const builtins = try self.requireBuiltins(); const dst = try self.genExpr(args[0]); const src = try self.genExpr(args[1]); const size_val = try self.genExpr(args[2]); const fn_ty = c.LLVMGlobalGetValueType(builtins.memcpy_fn); var call_args = [_]c.LLVMValueRef{ dst, src, size_val }; _ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.memcpy_fn, &call_args, 3, ""); return null; } fn genVectorExtract(self: *CodeGen, vec_val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef { if (field.len == 1) { const idx_val = componentToIndex(field[0]) orelse return self.emitErrorFmt("invalid vector component '{c}'", .{field[0]}); const idx = self.constInt32(idx_val); return c.LLVMBuildExtractElement(self.builder, vec_val, idx, "comp"); } return self.emitErrorFmt("unsupported vector swizzle '{s}'", .{field}); } fn genFieldAccess(self: *CodeGen, fa: ast.FieldAccess) !c.LLVMValueRef { // Check if the object is a struct or vector variable if (fa.object.data == .identifier) { if (self.named_values.get(fa.object.data.identifier.name)) |entry| { // Pointer auto-deref: p.field → p.*.field if (entry.ty.isPointer()) { const pointee_ty = self.resolveTypeFromName(entry.ty.pointer_type.pointee_name) orelse return self.emitError("unknown pointee type for auto-deref"); const loaded_ptr = c.LLVMBuildLoad2(self.builder, self.ptrType(), entry.ptr, "ptr_load"); if (pointee_ty.isStruct()) { const sname = pointee_ty.struct_type; const info = try self.getStructInfo(sname); const idx = self.findNameIndex(info.field_names, fa.field) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fa.field, sname }); const gep = self.structGEP(info.llvm_type, loaded_ptr, @intCast(idx), "pfield"); return self.loadTyped(info.field_types[idx], gep, "pfieldval"); } if (pointee_ty.isSlice()) { const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), loaded_ptr, "pslice_load"); return self.extractFatPtrField(slice_val, fa.field, "*slice"); } return self.emitErrorFmt("no field '{s}' on pointer", .{fa.field}); } if (entry.ty.isStruct()) { const sname = entry.ty.struct_type; const info = try self.getStructInfo(sname); const idx = try self.findFieldIndex(info.field_names, fa.field, sname); return self.loadStructField(info.llvm_type, entry.ptr, @intCast(idx), self.typeToLLVM(info.field_types[idx])); } if (entry.ty.isUnion()) { const uname = entry.ty.union_type; // C-style (untagged) union: bitcast pointer and load if (self.lookupUnionInfo(uname)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |fidx| { const field_ty = info.field_types[fidx]; return self.loadTyped(field_ty, entry.ptr, "union_field"); } // Check promoted fields from anonymous structs if (info.promoted_fields.get(fa.field)) |pf| { const sinfo = try self.getStructInfo(pf.struct_name); // GEP through union pointer as struct type, then access field return self.loadStructField(sinfo.llvm_type, entry.ptr, @intCast(pf.field_index), self.typeToLLVM(pf.field_type)); } return self.emitErrorFmt("no field '{s}' in union '{s}'", .{ fa.field, uname }); } // Tagged enum: GEP to payload area const info = try self.getTaggedEnumInfo(uname); // Find variant by name to determine payload type var vidx: ?usize = null; for (info.variant_names, 0..) |vn, i| { if (std.mem.eql(u8, vn, fa.field)) { vidx = i; break; } } const idx = vidx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ fa.field, uname }); const variant_ty = info.variant_types[idx]; if (variant_ty == .void_type) return self.emitErrorFmt("cannot access payload of void variant '{s}'", .{fa.field}); // GEP to payload area, load as variant type return self.loadStructField(info.llvm_type, entry.ptr, info.payload_field_index, self.typeToLLVM(variant_ty)); } if (entry.ty.isVector()) { const vec_val = self.loadTyped(entry.ty, entry.ptr, "vec_load"); return self.genVectorExtract(vec_val, fa.field); } if (entry.ty == .string_type) { const str_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "str_load"); return self.extractFatPtrField(str_val, fa.field, "string"); } if (entry.ty.isSlice()) { const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); return self.extractFatPtrField(slice_val, fa.field, "slice"); } if (entry.ty.isArray()) { if (std.mem.eql(u8, fa.field, "len")) { return self.constInt64(entry.ty.array_type.length); } return self.emitErrorFmt("no field '{s}' on array (available: .len)", .{fa.field}); } if (entry.ty.isAny()) { const any_val = c.LLVMBuildLoad2(self.builder, self.getAnyStructType(), entry.ptr, "any_load"); if (std.mem.eql(u8, fa.field, "tag")) { return self.extractValue(any_val, 0, "any_tag"); } if (std.mem.eql(u8, fa.field, "value")) { return self.extractValue(any_val, 1, "any_value"); } return self.emitErrorFmt("no field '{s}' on Any (available: .tag, .value)", .{fa.field}); } } } // Non-identifier object: evaluate expression and check type const obj_val = try self.genExpr(fa.object); const obj_ty = self.inferType(fa.object); if (obj_ty.isVector()) { return self.genVectorExtract(obj_val, fa.field); } if (obj_ty == .string_type) { return self.extractFatPtrField(obj_val, fa.field, "string"); } return self.emitError("field access on non-struct/non-vector expression"); } fn genVectorComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, vec_ty: Type, elem_ty: Type) c.LLVMValueRef { const vec_info = vec_ty.vector_type; const cmp = if (elem_ty.isFloat()) (if (op == .eq) c.LLVMBuildFCmp(self.builder, c.LLVMRealOEQ, lhs, rhs, "vcmp") else c.LLVMBuildFCmp(self.builder, c.LLVMRealONE, lhs, rhs, "vcmp")) else (if (op == .eq) self.icmp(c.LLVMIntEQ, lhs, rhs, "vcmp") else self.icmp(c.LLVMIntNE, lhs, rhs, "vcmp")); // Reduce: extract each i1 and AND (eq) or OR (neq) var result = c.LLVMBuildExtractElement(self.builder, cmp, self.constInt32(0), "cmp0"); for (1..vec_info.length) |i| { const elem = c.LLVMBuildExtractElement(self.builder, cmp, self.constInt32(@intCast(i)), "cmpi"); result = if (op == .eq) c.LLVMBuildAnd(self.builder, result, elem, "andcmp") else c.LLVMBuildOr(self.builder, result, elem, "orcmp"); } return result; } fn genIndexExpr(self: *CodeGen, ie: ast.IndexExpr) !c.LLVMValueRef { const obj_ty = self.inferType(ie.object); if (obj_ty.isVector()) { const vec_val = try self.genExpr(ie.object); const idx = try self.genExpr(ie.index); return c.LLVMBuildExtractElement(self.builder, vec_val, idx, "vidx"); } if (obj_ty.isArray()) { const arr_info = obj_ty.array_type; const elem_ty = try self.resolveElementType(arr_info.element_name, "array"); // Array index via identifier: load from GEP if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { const idx = try self.genExpr(ie.index); const gep = self.gepArrayElement(self.typeToLLVM(obj_ty), entry.ptr, idx, "arridx"); return self.loadTyped(elem_ty, gep, "arrval"); } } // Array index via field access: GEP through struct field if (ie.object.data == .field_access) { const field_ptr = try self.genAddressOf(ie.object); const idx = try self.genExpr(ie.index); const gep = self.gepArrayElement(self.typeToLLVM(obj_ty), field_ptr, idx, "field_arridx"); return self.loadTyped(elem_ty, gep, "field_arrval"); } } if (obj_ty == .string_type) { // String indexing: extract ptr from slice, GEP + load u8 const str_val = try self.genExpr(ie.object); const ptr = self.extractValue(str_val, 0, "str_ptr"); const idx = try self.genExpr(ie.index); const i8_type = self.i8Type(); const gep = self.gepPointerElement(i8_type, ptr, idx, "stridx"); return c.LLVMBuildLoad2(self.builder, i8_type, gep, "byte"); } if (obj_ty.isSlice()) { // Slice indexing: extract ptr, GEP with element type, load const slice_info = obj_ty.slice_type; const elem_ty = try self.resolveElementType(slice_info.element_name, "slice"); const elem_llvm_ty = self.typeToLLVM(elem_ty); // For identifier objects, load the slice from alloca if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); const ptr = self.extractValue(slice_val, 0, "slice_ptr"); const idx = try self.genExpr(ie.index); const gep = self.gepPointerElement(elem_llvm_ty, ptr, idx, "sliceidx"); return c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "sliceval"); } } // Fallback for non-identifier slice expressions const slice_val = try self.genExpr(ie.object); const ptr = self.extractValue(slice_val, 0, "slice_ptr"); const idx = try self.genExpr(ie.index); const gep = self.gepPointerElement(elem_llvm_ty, ptr, idx, "sliceidx"); return c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "sliceval"); } // Many-pointer indexing: [*]T — GEP + load if (obj_ty.isManyPointer()) { const elem_ty = self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse return self.emitError("unknown many-pointer element type"); const elem_llvm_ty = self.typeToLLVM(elem_ty); const ptr_val = try self.genExpr(ie.object); const idx = try self.genExpr(ie.index); const gep = self.gepPointerElement(elem_llvm_ty, ptr_val, idx, "mptridx"); return c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "mptrval"); } return self.emitError("index expression requires an array, vector, string, slice, or [*] pointer"); } fn genSliceExpr(self: *CodeGen, se: ast.SliceExpr) !c.LLVMValueRef { const obj_ty = self.inferType(se.object); const i64_ty = self.i64Type(); const zero = c.LLVMConstInt(i64_ty, 0, 0); const slice_struct_ty = self.getStringStructType(); // Resolve start (default: 0) const start_val = if (se.start) |s| try self.genExpr(s) else zero; if (obj_ty.isArray()) { const arr_info = obj_ty.array_type; // Resolve end (default: array length) const end_val = if (se.end) |e| try self.genExpr(e) else c.LLVMConstInt(i64_ty, arr_info.length, 0); // Get array alloca const arr_ptr = blk: { if (se.object.data == .identifier) { if (self.named_values.get(se.object.data.identifier.name)) |entry| { break :blk entry.ptr; } } break :blk try self.genExpr(se.object); }; // GEP to arr[start] var indices = [_]c.LLVMValueRef{ zero, start_val }; const elem_ptr = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(obj_ty), arr_ptr, &indices, 2, "slice_start"); // len = end - start const len_val = c.LLVMBuildSub(self.builder, end_val, start_val, "slice_len"); // Build {ptr, len} return self.buildFatPointer(slice_struct_ty, elem_ptr, len_val); } if (obj_ty == .string_type or obj_ty.isSlice()) { // Load {ptr, len} from variable or expression const obj_val = blk: { if (se.object.data == .identifier) { if (self.named_values.get(se.object.data.identifier.name)) |entry| { break :blk c.LLVMBuildLoad2(self.builder, slice_struct_ty, entry.ptr, "sslice_load"); } } break :blk try self.genExpr(se.object); }; const base_ptr = self.extractValue(obj_val, 0, "sslice_ptr"); const base_len = self.extractValue(obj_val, 1, "sslice_len"); // Resolve end (default: original length) const end_val = if (se.end) |e| try self.genExpr(e) else base_len; // GEP base_ptr + start const elem_llvm_ty = if (obj_ty == .string_type) self.i8Type() else self.typeToLLVM(Type.fromName(obj_ty.slice_type.element_name) orelse return self.emitError("unknown slice element type")); const new_ptr = self.gepPointerElement(elem_llvm_ty, base_ptr, start_val, "sslice_off"); // len = end - start const len_val = c.LLVMBuildSub(self.builder, end_val, start_val, "sslice_len"); // Build {ptr, len} return self.buildFatPointer(slice_struct_ty, new_ptr, len_val); } return self.emitError("slice expression requires an array, string, or slice"); } fn genBinaryOp(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, result_type: Type) c.LLVMValueRef { // For vectors, dispatch based on element type; LLVM handles element-wise ops automatically const effective_ty = if (result_type.isVector()) result_type.vectorElementType() orelse return lhs else result_type; // Vector comparison needs special handling (returns vector of i1) if (result_type.isVector() and (op == .eq or op == .neq)) { return self.genVectorComparison(op, lhs, rhs, result_type, effective_ty); } const b = self.builder; const is_float = effective_ty.isFloat(); const is_unsigned = effective_ty.isUnsigned(); return switch (op) { .add => if (is_float) c.LLVMBuildFAdd(b, lhs, rhs, "addtmp") else c.LLVMBuildAdd(b, lhs, rhs, "addtmp"), .sub => if (is_float) c.LLVMBuildFSub(b, lhs, rhs, "subtmp") else c.LLVMBuildSub(b, lhs, rhs, "subtmp"), .mul => if (is_float) c.LLVMBuildFMul(b, lhs, rhs, "multmp") else c.LLVMBuildMul(b, lhs, rhs, "multmp"), .div => if (is_float) c.LLVMBuildFDiv(b, lhs, rhs, "divtmp") else if (is_unsigned) c.LLVMBuildUDiv(b, lhs, rhs, "divtmp") else c.LLVMBuildSDiv(b, lhs, rhs, "divtmp"), .mod => if (is_float) c.LLVMBuildFRem(b, lhs, rhs, "modtmp") else if (is_unsigned) c.LLVMBuildURem(b, lhs, rhs, "modtmp") else c.LLVMBuildSRem(b, lhs, rhs, "modtmp"), .eq => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOEQ, lhs, rhs, "eqtmp") else c.LLVMBuildICmp(b, c.LLVMIntEQ, lhs, rhs, "eqtmp"), .neq => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealONE, lhs, rhs, "neqtmp") else c.LLVMBuildICmp(b, c.LLVMIntNE, lhs, rhs, "neqtmp"), .lt => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOLT, lhs, rhs, "lttmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntULT, lhs, rhs, "lttmp") else c.LLVMBuildICmp(b, c.LLVMIntSLT, lhs, rhs, "lttmp"), .lte => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOLE, lhs, rhs, "letmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntULE, lhs, rhs, "letmp") else c.LLVMBuildICmp(b, c.LLVMIntSLE, lhs, rhs, "letmp"), .gt => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOGT, lhs, rhs, "gttmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntUGT, lhs, rhs, "gttmp") else c.LLVMBuildICmp(b, c.LLVMIntSGT, lhs, rhs, "gttmp"), .gte => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOGE, lhs, rhs, "getmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntUGE, lhs, rhs, "getmp") else c.LLVMBuildICmp(b, c.LLVMIntSGE, lhs, rhs, "getmp"), .bit_and => c.LLVMBuildAnd(b, lhs, rhs, "bandtmp"), .bit_or => c.LLVMBuildOr(b, lhs, rhs, "bortmp"), .and_op, .or_op => unreachable, }; } fn genShortCircuitOp(self: *CodeGen, binop: ast.BinaryOp, is_and: bool) !c.LLVMValueRef { const lhs_val = self.valueToBool(try self.genExpr(binop.lhs)); const lhs_bb = self.getCurrentBlock(); const rhs_label: [*c]const u8 = if (is_and) "and.rhs" else "or.rhs"; const merge_label: [*c]const u8 = if (is_and) "and.merge" else "or.merge"; const rhs_bb = self.appendBB(rhs_label); const merge_bb = self.appendBB(merge_label); // AND: true → evaluate rhs, false → short-circuit to merge // OR: true → short-circuit to merge, false → evaluate rhs if (is_and) self.condBr(lhs_val, rhs_bb, merge_bb) else self.condBr(lhs_val, merge_bb, rhs_bb); self.positionAt(rhs_bb); const rhs_val = self.valueToBool(try self.genExpr(binop.rhs)); const rhs_end_bb = self.getCurrentBlock(); self.br(merge_bb); self.positionAt(merge_bb); const short_circuit_val: u64 = if (is_and) 0 else 1; const result_label: [*c]const u8 = if (is_and) "and.result" else "or.result"; const i1_ty = self.i1Type(); const phi = c.LLVMBuildPhi(self.builder, i1_ty, result_label); var vals = [2]c.LLVMValueRef{ c.LLVMConstInt(i1_ty, short_circuit_val, 0), rhs_val }; var blocks = [2]c.LLVMBasicBlockRef{ lhs_bb, rhs_end_bb }; c.LLVMAddIncoming(phi, &vals, &blocks, 2); return phi; } fn genChainedComparison(self: *CodeGen, chain: ast.ChainedComparison) !c.LLVMValueRef { // Evaluate all operands exactly once var operand_vals = std.ArrayList(c.LLVMValueRef).empty; for (chain.operands) |operand| { const val = try self.genExpr(operand); try operand_vals.append(self.allocator, val); } // Compare pairwise and AND results together var result: c.LLVMValueRef = undefined; for (chain.ops, 0..) |op, i| { const lhs_ty = self.inferType(chain.operands[i]); const rhs_ty = self.inferType(chain.operands[i + 1]); const cmp_type = Type.widen(lhs_ty, rhs_ty); const lhs_conv = self.convertValue(operand_vals.items[i], lhs_ty, cmp_type); const rhs_conv = self.convertValue(operand_vals.items[i + 1], rhs_ty, cmp_type); const cmp = self.genBinaryOp(op, lhs_conv, rhs_conv, cmp_type); if (i == 0) { result = cmp; } else { result = c.LLVMBuildAnd(self.builder, result, cmp, "chain.and"); } } return result; } fn genCall(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.callee.data == .field_access) { const fa = call_node.callee.data.field_access; // Union construction: Shape.variant(payload) const resolved_type: ?Type = blk: { if (fa.object.data == .identifier) { const name = self.resolveAlias(fa.object.data.identifier.name); if (self.type_registry.get(name)) |e| switch (e) { .tagged_enum => break :blk Type{ .union_type = name }, .struct_info => break :blk Type{ .struct_type = name }, else => {}, }; } else { const ty = self.resolveType(fa.object); if (ty.isUnion() or ty.isStruct()) break :blk ty; } break :blk null; }; if (resolved_type) |rty| { if (rty.isUnion()) { const type_name = rty.union_type; const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null; return self.genTaggedEnumLiteral(.{ .name = fa.field, .payload = payload_node, }, type_name); } } // Namespaced call: namespace.func(args) if (fa.object.data == .identifier) { const ns_name = fa.object.data.identifier.name; if (self.namespaces.contains(ns_name)) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field }); return self.genCallByName(qualified, call_node); } } // UFCS: obj.method(args...) → method(obj, args...) const method_name = fa.field; const method_z = self.allocator.dupeZ(u8, method_name) catch method_name; if (self.generic_templates.contains(method_name) or c.LLVMGetNamedFunction(self.module, method_z.ptr) != null) { var ufcs_args = try self.allocator.alloc(*Node, call_node.args.len + 1); ufcs_args[0] = fa.object; for (call_node.args, 0..) |arg, i| { ufcs_args[i + 1] = arg; } return self.genCallByName(method_name, .{ .callee = call_node.callee, .args = ufcs_args, }); } } // Resolve callee — must be an identifier if (call_node.callee.data != .identifier) return self.emitError("callee must be an identifier"); const callee_name = call_node.callee.data.identifier.name; return self.genCallByName(callee_name, call_node); } fn genCallByName(self: *CodeGen, callee_name: []const u8, call_node: ast.Call) !c.LLVMValueRef { // Check if this is a generic function call if (self.generic_templates.get(callee_name)) |template| { return self.genGenericCall(callee_name, template, call_node); } // Intra-namespace fallback for generic templates if (self.current_namespace) |ns| { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); if (self.generic_templates.get(qualified)) |template| { return self.genGenericCall(qualified, template, call_node); } } // Check for #builtin function (only available when imported) if (self.builtin_functions.contains(callee_name)) { return self.dispatchBuiltin(callee_name, call_node); } // Intra-namespace fallback for builtins if (self.current_namespace) |ns| { const qualified_builtin = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); if (self.builtin_functions.contains(qualified_builtin)) { return self.dispatchBuiltin(qualified_builtin, call_node); } } // Compiler intrinsics (always available, no #builtin declaration needed) if (std.mem.eql(u8, callee_name, "sqrt")) { return self.genMathIntrinsic(call_node, "sqrt"); } if (std.mem.eql(u8, callee_name, "sin")) { return self.genMathIntrinsic(call_node, "sin"); } if (std.mem.eql(u8, callee_name, "cos")) { return self.genMathIntrinsic(call_node, "cos"); } if (std.mem.eql(u8, callee_name, "cast")) { return self.genCast(call_node); } if (std.mem.eql(u8, callee_name, "malloc")) { return self.genMalloc(call_node.args); } if (std.mem.eql(u8, callee_name, "free")) { return self.genFree(call_node.args); } if (std.mem.eql(u8, callee_name, "memcpy")) { return self.genMemcpy(call_node.args); } var nbuf: [256]u8 = undefined; var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &nbuf)); // Foreign function fallback: qualified name "ns.Func" → try unqualified "Func" (the C symbol) if (callee_fn == null) { if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |dot_idx| { const base_name = callee_name[dot_idx + 1 ..]; var bbuf: [256]u8 = undefined; callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(base_name, &bbuf)); } } // Intra-namespace fallback: try qualified name if (callee_fn == null) { if (self.current_namespace) |ns| { const qualified2 = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); var qbuf: [256]u8 = undefined; callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf)); } } // Function pointer indirect call: callee is a variable with function_type if (callee_fn == null) { if (self.lookupValue(callee_name)) |v| { const entry = v.asNamedValue(); if (entry) |e| { if (e.ty.isFunctionType()) { return self.genIndirectCall(e, call_node); } } } return self.emitErrorFmt("undefined function '{s}'", .{callee_name}); } // Get function type (opaque pointers: use LLVMGlobalGetValueType) const fn_type = c.LLVMGlobalGetValueType(callee_fn.?); // Check if this is a variadic function call const var_info = self.variadic_functions.get(callee_name) orelse blk: { // Try qualified name lookup if (self.current_namespace) |ns| { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); break :blk self.variadic_functions.get(qualified); } break :blk null; }; // Generate arguments with type conversion to match parameter types const num_params = c.LLVMCountParamTypes(fn_type); var param_llvm_types: [64]c.LLVMTypeRef = undefined; if (num_params > 0) { c.LLVMGetParamTypes(fn_type, ¶m_llvm_types); } var arg_vals = std.ArrayList(c.LLVMValueRef).empty; if (var_info) |vi| { // Variadic call: generate fixed args, then pack remaining into slice const fixed_count = vi.fixed_param_count; // Generate fixed args for (0..fixed_count) |i| { if (i < call_node.args.len) { const param_ty = self.llvmTypeToSxType(param_llvm_types[i]); try arg_vals.append(self.allocator, try self.genExprAsType(call_node.args[i], param_ty)); } } // Pack variadic args into a slice {ptr, len} const elem_ty = Type.fromName(vi.element_type_name) orelse Type.s(64); const elem_llvm_ty = self.typeToLLVM(elem_ty); const var_arg_count = if (call_node.args.len > fixed_count) call_node.args.len - fixed_count else 0; // Check for spread operator: fn(..array) — single spread arg if (var_arg_count == 1 and call_node.args[fixed_count].data == .spread_expr) { const spread_operand = call_node.args[fixed_count].data.spread_expr.operand; const spread_ty = self.inferType(spread_operand); if (spread_ty.isArray()) { // Spread an array: construct slice from array pointer + known length const arr_info = spread_ty.array_type; if (spread_operand.data == .identifier) { if (self.named_values.get(spread_operand.data.identifier.name)) |entry| { const arr_llvm_ty = self.typeToLLVM(spread_ty); const arr_ptr = self.arrayDecayToPointer(arr_llvm_ty, entry.ptr, "spread_ptr"); const len_val = self.constInt64(arr_info.length); const slice_val = self.buildStringSlice(arr_ptr, len_val); try arg_vals.append(self.allocator, slice_val); } else { return self.emitError("spread operand not found"); } } else { return self.emitError("spread operator requires a named variable"); } } else if (spread_ty.isSlice()) { // Spread a slice: pass through as-is const slice_val = try self.genExpr(spread_operand); try arg_vals.append(self.allocator, slice_val); } else { return self.emitError("spread operator requires an array or slice"); } } else if (var_arg_count > 0) { // Allocate array on stack: [N x elem_type] const arr_ty = c.LLVMArrayType2(elem_llvm_ty, @intCast(var_arg_count)); const arr_alloca = self.buildEntryBlockAlloca(arr_ty, "varargs_arr"); // Store each variadic arg for (0..var_arg_count) |vi_idx| { const arg_val = if (elem_ty.isAny()) blk: { // ..Any: wrap each arg in Any{tag, value} const raw_val = try self.genExpr(call_node.args[fixed_count + vi_idx]); const arg_ty = self.inferType(call_node.args[fixed_count + vi_idx]); break :blk try self.buildAnyValue(raw_val, arg_ty); } else try self.genExprAsType(call_node.args[fixed_count + vi_idx], elem_ty); const gep = self.gepArrayElement(arr_ty, arr_alloca, self.constInt32(@intCast(vi_idx)), "vararg_elem"); _ = c.LLVMBuildStore(self.builder, arg_val, gep); } // Build slice: {ptr, len} const arr_ptr = self.arrayDecayToPointer(arr_ty, arr_alloca, "varargs_ptr"); const len_val = self.constInt64(@intCast(var_arg_count)); const slice_val = self.buildStringSlice(arr_ptr, len_val); try arg_vals.append(self.allocator, slice_val); } else { // Zero variadic args: pass empty slice {null, 0} const null_ptr = c.LLVMConstNull(self.ptrType()); const zero_len = self.constInt64(0); const slice_val = self.buildStringSlice(null_ptr, zero_len); try arg_vals.append(self.allocator, slice_val); } } else { // Normal (non-variadic) call — use stored sx param types when available const stored_param_types = self.fn_param_types.get(callee_name) orelse blk: { if (self.current_namespace) |ns| { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); break :blk self.fn_param_types.get(qualified); } break :blk null; }; for (call_node.args, 0..) |arg, i| { if (i < num_params) { const param_ty = if (stored_param_types != null and i < stored_param_types.?.len) stored_param_types.?[i] else self.llvmTypeToSxType(param_llvm_types[i]); // For #foreign functions, [:0]u8 params have LLVM type ptr const arg_ty = self.inferType(arg); const llvm_param_is_ptr = (i < num_params and c.LLVMGetTypeKind(param_llvm_types[i]) == c.LLVMPointerTypeKind); const ptr_ty = Type{ .pointer_type = .{ .pointee_name = "u8" } }; var val = if (llvm_param_is_ptr and arg.data == .string_literal) blk: { // String literal → pointer: produce raw ptr directly (context-dependent) break :blk try self.genExprAsType(arg, ptr_ty); } else if (llvm_param_is_ptr and arg_ty == .string_type) blk: { // String variable → pointer: extract .ptr from {ptr, len} const str_val = try self.genExpr(arg); break :blk self.extractValue(str_val, 0, "str_ptr"); } else if ((param_ty.isPointer() or llvm_param_is_ptr) and arg_ty.isSlice() and std.mem.eql(u8, arg_ty.slice_type.element_name, "u8")) { return self.emitError( "cannot pass []u8 to *u8: slice may not be null-terminated; use a string literal or xx cast", ); } else try self.genExprAsType(arg, param_ty); // Foreign calls: convert struct values to C ABI representation if (param_ty.isStruct() and self.foreign_fns.contains(callee_name)) { const struct_llvm_ty = self.typeToLLVM(param_ty); if (struct_llvm_ty != param_llvm_types[i]) { val = self.convertStructToABI(val, struct_llvm_ty, param_llvm_types[i]); } } try arg_vals.append(self.allocator, val); } else { try arg_vals.append(self.allocator, try self.genExpr(arg)); } } } const args_slice = try arg_vals.toOwnedSlice(self.allocator); const ret_ty = c.LLVMGetReturnType(fn_type); const call_name: [*c]const u8 = if (ret_ty == self.voidType()) "" else "calltmp"; return c.LLVMBuildCall2( self.builder, fn_type, callee_fn.?, if (args_slice.len > 0) args_slice.ptr else null, @intCast(args_slice.len), call_name, ); } fn genIndirectCall(self: *CodeGen, entry: NamedValue, call_node: ast.Call) !c.LLVMValueRef { const fti = entry.ty.function_type; // Load the function pointer from the alloca const ptr_ty = self.ptrType(); const fn_ptr = c.LLVMBuildLoad2(self.builder, ptr_ty, entry.ptr, "fn_ptr"); // Build LLVM function type from FunctionTypeInfo const ptr_ty_llvm = self.ptrType(); var param_llvm_types: [64]c.LLVMTypeRef = undefined; for (fti.param_types, 0..) |pt, i| { // [N]T and [:0]T params are pointers at the ABI level param_llvm_types[i] = if (pt.isArray() or pt == .string_type) ptr_ty_llvm else self.typeToLLVM(pt); } const ret_llvm = self.typeToLLVM(fti.return_type.*); const fn_type = c.LLVMFunctionType( ret_llvm, if (fti.param_types.len > 0) ¶m_llvm_types else null, @intCast(fti.param_types.len), 0, ); // Generate arguments with type conversion var arg_vals = std.ArrayList(c.LLVMValueRef).empty; for (call_node.args, 0..) |arg, i| { if (i < fti.param_types.len) { const pt = fti.param_types[i]; // [N]T params: pass pointer via array decay if (pt.isArray()) { const decay_target: Type = .{ .many_pointer_type = .{ .element_name = pt.array_type.element_name } }; try arg_vals.append(self.allocator, try self.genExprAsType(arg, decay_target)); } else if (pt == .string_type) { // [:0]u8 params: extract .ptr from fat pointer const val = try self.genExprAsType(arg, pt); try arg_vals.append(self.allocator, self.extractValue(val, 0, "str_ptr")); } else { try arg_vals.append(self.allocator, try self.genExprAsType(arg, pt)); } } else { try arg_vals.append(self.allocator, try self.genExpr(arg)); } } const args_slice = try arg_vals.toOwnedSlice(self.allocator); const call_name: [*c]const u8 = if (ret_llvm == self.voidType()) "" else "calltmp"; return c.LLVMBuildCall2( self.builder, fn_type, fn_ptr, if (args_slice.len > 0) args_slice.ptr else null, @intCast(args_slice.len), call_name, ); } fn genGenericCall(self: *CodeGen, qualified_name: []const u8, template: ast.FnDecl, call_node: ast.Call) !c.LLVMValueRef { const fd = template; // Check for runtime type dispatch: cast(runtime_type_var, any_val) as argument if (self.current_match_tags) |match_tags| { if (match_tags.len > 0) { for (call_node.args) |arg| { if (arg.data == .call) { if (arg.data.call.callee.data == .identifier) { const cast_name = arg.data.call.callee.data.identifier.name; if (std.mem.eql(u8, cast_name, "cast") or std.mem.eql(u8, cast_name, "std.cast")) { if (arg.data.call.args.len == 2) { const type_arg = arg.data.call.args[0]; // Check if first arg of cast is a runtime variable (not a type expression) if (type_arg.data == .identifier) { const name = type_arg.data.identifier.name; // It's a runtime type if it's a named_value, not a type name if (self.named_values.contains(name) and Type.fromName(name) == null and !self.type_registry.contains(name)) { return self.genGenericCallWithRuntimeDispatch(template, call_node, match_tags); } } } } } } } } } // Check for comptime value params var has_comptime_values = false; var comptime_nodes = std.StringHashMap(*Node).init(self.allocator); for (fd.type_params) |tp| { const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; if (!std.mem.eql(u8, constraint_name, "Type")) { // Value param — extract comptime value from call arg has_comptime_values = true; for (fd.params, 0..) |param, pi| { if (std.mem.eql(u8, param.name, tp.name)) { if (pi < call_node.args.len) { try comptime_nodes.put(tp.name, @constCast(call_node.args[pi])); } break; } } } } // Normal generic call: Infer type bindings from arguments, widening across all args for the same type param var bindings = std.StringHashMap(Type).init(self.allocator); // Track bindings derived from parameterized struct types — these are authoritative and should not be widened var firm_bindings = std.StringHashMap(void).init(self.allocator); for (fd.params, 0..) |param, i| { if (param.is_comptime) continue; // Direct type param: (a: $T) introduces/widens, (a: T) only binds if not yet bound if (param.type_expr.data == .type_expr) { const type_name = param.type_expr.data.type_expr.name; // Check if this type name is a type parameter for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, type_name)) { if (i < call_node.args.len) { // Skip widening if binding was derived from a parameterized struct if (!firm_bindings.contains(type_name)) { const arg_ty = self.inferType(call_node.args[i]); if (bindings.get(type_name)) |existing| { try bindings.put(type_name, Type.widen(existing, arg_ty)); } else { try bindings.put(type_name, arg_ty); } } } break; } } } // Pointer to parameterized type: (p: *Foo($T)) — extract T from concrete struct if (param.type_expr.data == .pointer_type_expr) { const pointee = param.type_expr.data.pointer_type_expr.pointee_type; if (pointee.data == .parameterized_type_expr) { const pte = pointee.data.parameterized_type_expr; if (i < call_node.args.len) { const arg_ty = self.inferType(call_node.args[i]); // arg should be *StructName — get the struct's stored type param bindings const struct_name = if (arg_ty.isPointer()) arg_ty.pointer_type.pointee_name else if (arg_ty.isStruct()) arg_ty.struct_type else ""; if (self.lookupStructInfo(struct_name)) |info| { if (info.template_name) |tmpl_name| { if (std.mem.eql(u8, tmpl_name, pte.name)) { // Match generic args against stored type param bindings for (pte.args, 0..) |arg, ai| { if (arg.data == .type_expr and arg.data.type_expr.is_generic) { const gen_name = arg.data.type_expr.name; if (ai < info.type_param_types.len) { try bindings.put(gen_name, info.type_param_types[ai]); try firm_bindings.put(gen_name, {}); } } } } } } } } } // Slice type param: (items: []$T) — infer T from array or slice element type if (param.type_expr.data == .slice_type_expr) { const elem_node = param.type_expr.data.slice_type_expr.element_type; if (elem_node.data == .type_expr) { const type_name = elem_node.data.type_expr.name; for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, type_name)) { if (i < call_node.args.len) { const arg_ty = self.inferType(call_node.args[i]); const elem_ty = if (arg_ty.isArray()) Type.fromName(arg_ty.array_type.element_name) orelse arg_ty else if (arg_ty.isSlice()) Type.fromName(arg_ty.slice_type.element_name) orelse arg_ty else arg_ty; if (bindings.get(type_name)) |existing| { try bindings.put(type_name, Type.widen(existing, elem_ty)); } else { try bindings.put(type_name, elem_ty); } } break; } } } } } if (has_comptime_values) { return self.genComptimeCall(qualified_name, fd, call_node, bindings, comptime_nodes); } // Generate mangled name const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings, null, null); // Check cache const callee_fn = if (self.generic_instances.get(mangled)) |cached| cached else try self.instantiateGeneric(fd, bindings, mangled); // Generate arguments with type conversion to match parameter types const saved_call_bindings = self.type_param_bindings; self.type_param_bindings = bindings; var arg_vals = std.ArrayList(c.LLVMValueRef).empty; for (call_node.args, 0..) |arg, i| { if (i < fd.params.len) { const param_ty = self.resolveType(fd.params[i].type_expr); try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty)); } else { try arg_vals.append(self.allocator, try self.genExpr(arg)); } } self.type_param_bindings = saved_call_bindings; const args_slice = try arg_vals.toOwnedSlice(self.allocator); const fn_type = c.LLVMGlobalGetValueType(callee_fn); const ret_ty = c.LLVMGetReturnType(fn_type); const call_name: [*c]const u8 = if (ret_ty == self.voidType()) "" else "calltmp"; return c.LLVMBuildCall2( self.builder, fn_type, callee_fn, if (args_slice.len > 0) args_slice.ptr else null, @intCast(args_slice.len), call_name, ); } /// Generate a call to a generic function with comptime value parameters. /// Instantiates the function with the specific comptime values, then delegates to genCallByName /// with the mangled name and adjusted args (comptime args removed). fn genComptimeCall( self: *CodeGen, qualified_name: []const u8, fd: ast.FnDecl, call_node: ast.Call, type_bindings: std.StringHashMap(Type), comptime_nodes: std.StringHashMap(*Node), ) !c.LLVMValueRef { const mangled = try self.mangleGenericName(qualified_name, fd.type_params, type_bindings, null, comptime_nodes); // Instantiate if not cached if (!self.generic_instances.contains(mangled)) { // Set comptime param nodes for #insert substitution const saved_comptime_nodes = self.comptime_param_nodes; self.comptime_param_nodes = comptime_nodes; defer self.comptime_param_nodes = saved_comptime_nodes; // Set namespace context if the qualified name is namespaced (e.g. "std.print") const saved_namespace = self.current_namespace; if (std.mem.indexOfScalar(u8, qualified_name, '.')) |dot_pos| { self.current_namespace = qualified_name[0..dot_pos]; } defer self.current_namespace = saved_namespace; // Pre-register Any type IDs for variadic args before function instantiation, // so type category matching (case slice:, case array:, etc.) in any_to_string // can find registered types during compilation of the function body. for (fd.params, 0..) |param, pi| { if (param.is_variadic) { const elem_name_raw = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else ""; if (std.mem.eql(u8, elem_name_raw, "Any") or std.mem.eql(u8, elem_name_raw, "std.Any")) { for (pi..call_node.args.len) |ai| { const arg_ty = self.inferType(call_node.args[ai]); try self.preRegisterAnyType(arg_ty); } } break; } } _ = try self.instantiateGeneric(fd, type_bindings, mangled); // Register variadic info for the mangled function (adjusted for removed comptime params) var comptime_before_variadic: u32 = 0; for (fd.params) |param| { if (param.is_variadic) break; if (param.is_comptime) comptime_before_variadic += 1; } for (fd.params, 0..) |param, i| { if (param.is_variadic) { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; try self.variadic_functions.put(mangled, .{ .fixed_param_count = @intCast(i - comptime_before_variadic), .element_type_name = elem_name, }); break; } } } // Build adjusted call args (skip comptime args) var adjusted_args = std.ArrayList(*Node).empty; for (call_node.args, 0..) |arg, i| { if (i < fd.params.len and fd.params[i].is_comptime) continue; try adjusted_args.append(self.allocator, @constCast(arg)); } const adjusted_args_slice = try adjusted_args.toOwnedSlice(self.allocator); const adjusted_call = ast.Call{ .callee = call_node.callee, .args = adjusted_args_slice, }; // Call the instantiated function through normal path (handles variadic packing etc.) return self.genCallByName(mangled, adjusted_call); } /// Generate a generic function call with runtime type dispatch. /// For each type tag in match_tags, monomorphize the generic function and dispatch via switch. fn genGenericCallWithRuntimeDispatch( self: *CodeGen, template: ast.FnDecl, call_node: ast.Call, match_tags: []const u64, ) !c.LLVMValueRef { const fd = template; // Find the cast argument and extract the runtime type tag + any value source var cast_arg_idx: usize = 0; var type_tag_node: *Node = undefined; var any_val_node: *Node = undefined; for (call_node.args, 0..) |arg, i| { if (arg.data == .call and arg.data.call.callee.data == .identifier) { const name = arg.data.call.callee.data.identifier.name; if ((std.mem.eql(u8, name, "cast") or std.mem.eql(u8, name, "std.cast")) and arg.data.call.args.len == 2) { cast_arg_idx = i; type_tag_node = arg.data.call.args[0]; any_val_node = arg.data.call.args[1]; break; } } } // Generate the runtime type tag value and the Any value const type_tag_val = try self.genExpr(type_tag_node); const any_val = try self.genExpr(any_val_node); // Generate non-cast arguments (evaluated once, before the switch) var other_arg_vals = std.ArrayList(?c.LLVMValueRef).empty; for (call_node.args, 0..) |arg, i| { if (i == cast_arg_idx) { try other_arg_vals.append(self.allocator, null); // placeholder } else { try other_arg_vals.append(self.allocator, try self.genExpr(arg)); } } // Extract Any value i64 BEFORE the switch (switch is a terminator, nothing can follow it in the same BB) const any_i64 = self.extractValue(any_val, 1, "any_payload"); // Build dispatch switch const sb = self.buildSwitch(type_tag_val, @intCast(match_tags.len), "dispatch_merge", "dispatch_default"); // Determine return type from function signature const ret_ty = if (fd.return_type) |rt| self.resolveType(rt) else Type.void_type; // We'll use the first monomorphized function's return to determine LLVM type var result_llvm_ty: c.LLVMTypeRef = null; var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; for (match_tags) |tag| { // Find the AnyTypeEntry for this tag var entry_type: ?Type = null; var it = self.any_type_entries.iterator(); while (it.next()) |entry| { if (entry.value_ptr.tag_id == tag) { entry_type = entry.value_ptr.sx_type; break; } } const sx_type = entry_type orelse continue; // Create case BB const case_bb = self.appendBB("dispatch_case"); c.LLVMAddCase(sb.sw, self.constInt64(tag), case_bb); self.positionAt(case_bb); // Convert Any payload to the concrete type const concrete_val = try self.extractAnyToConcreteType(any_i64, sx_type); // Monomorphize the generic function with this type var bindings = std.StringHashMap(Type).init(self.allocator); // Bind the type parameter from the cast argument's position if (cast_arg_idx < fd.params.len) { if (fd.params[cast_arg_idx].type_expr.data == .type_expr) { const tp_name = fd.params[cast_arg_idx].type_expr.data.type_expr.name; for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, tp_name)) { try bindings.put(tp.name, sx_type); break; } } } else if (fd.params[cast_arg_idx].type_expr.data == .slice_type_expr) { // Slice type param: (items: []$T) — extract element type from concrete slice const elem_node = fd.params[cast_arg_idx].type_expr.data.slice_type_expr.element_type; if (elem_node.data == .type_expr) { const tp_name = elem_node.data.type_expr.name; for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, tp_name)) { // Extract element type from concrete slice type const elem_ty = if (sx_type.isSlice()) Type.fromName(sx_type.slice_type.element_name) orelse sx_type else if (sx_type.isArray()) Type.fromName(sx_type.array_type.element_name) orelse sx_type else sx_type; try bindings.put(tp.name, elem_ty); break; } } } } } const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings, null, null); const callee_fn = if (self.generic_instances.get(mangled)) |cached| cached else try self.instantiateGeneric(fd, bindings, mangled); // Build argument list self.type_param_bindings = bindings; var arg_vals_list = std.ArrayList(c.LLVMValueRef).empty; for (other_arg_vals.items, 0..) |maybe_val, ai| { if (ai == cast_arg_idx) { // Use the converted concrete value try arg_vals_list.append(self.allocator, concrete_val); } else if (maybe_val) |v| { try arg_vals_list.append(self.allocator, v); } } self.type_param_bindings = null; const args_slice = try arg_vals_list.toOwnedSlice(self.allocator); const fn_type = c.LLVMGlobalGetValueType(callee_fn); const call_result = c.LLVMBuildCall2( self.builder, fn_type, callee_fn, if (args_slice.len > 0) args_slice.ptr else null, @intCast(args_slice.len), if (ret_ty != .void_type) "dispatch_result" else "", ); if (result_llvm_ty == null and ret_ty != .void_type) { result_llvm_ty = c.LLVMTypeOf(call_result); } if (ret_ty != .void_type) { try phi_vals.append(self.allocator, call_result); try phi_bbs.append(self.allocator, self.getCurrentBlock()); } self.br(sb.merge_bb); } // Default case: return undef (should not be reached) self.positionAt(sb.default_bb); if (ret_ty != .void_type and result_llvm_ty != null) { try phi_vals.append(self.allocator, self.getUndef(result_llvm_ty.?)); try phi_bbs.append(self.allocator, sb.default_bb); } self.br(sb.merge_bb); // Merge self.positionAt(sb.merge_bb); if (ret_ty != .void_type and result_llvm_ty != null) { const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, result_llvm_ty.?, "dispatch_phi"); return phi; } return null; } /// Extract a concrete typed value from an Any i64 payload. fn extractAnyToConcreteType(self: *CodeGen, any_i64: c.LLVMValueRef, sx_type: Type) !c.LLVMValueRef { return switch (sx_type) { .boolean => self.trunc(any_i64, self.i1Type(), "any_to_bool"), .signed => |w| if (w <= 32) self.trunc(any_i64, c.LLVMIntTypeInContext(self.context, w), "any_to_int") else any_i64, .unsigned => |w| if (w <= 32) self.trunc(any_i64, c.LLVMIntTypeInContext(self.context, w), "any_to_uint") else any_i64, .f32 => blk: { const as_f64 = self.bitCast(any_i64, self.f64Type(), "i64_to_f64"); break :blk c.LLVMBuildFPTrunc(self.builder, as_f64, self.f32Type(), "any_to_f32"); }, .f64 => self.bitCast(any_i64, self.f64Type(), "any_to_f64"), .string_type => self.loadFromI64Ptr(any_i64, self.getStringStructType(), "any_to_str"), .struct_type => |sname| blk: { const info = try self.getStructInfo(sname); break :blk self.loadFromI64Ptr(any_i64, info.llvm_type, "any_to_struct"); }, .enum_type => |ename| blk: { const enum_llvm_ty = self.getEnumLLVMType(ename); const enum_bits = c.LLVMGetIntTypeWidth(enum_llvm_ty); if (enum_bits < 64) break :blk self.trunc(any_i64, enum_llvm_ty, "any_to_enum") else break :blk any_i64; }, .union_type => |uname| blk: { const info = try self.getTaggedEnumInfo(uname); break :blk self.loadFromI64Ptr(any_i64, info.llvm_type, "any_to_union"); }, .vector_type, .array_type => blk: { const llvm_ty = self.typeToLLVM(sx_type); break :blk self.loadFromI64Ptr(any_i64, llvm_ty, "any_to_vec"); }, .slice_type => self.loadFromI64Ptr(any_i64, self.getStringStructType(), "any_to_slice"), .pointer_type, .many_pointer_type => self.intToPtr(any_i64, "any_to_ptr"), else => any_i64, }; } fn mangleGenericName( self: *CodeGen, base: []const u8, type_params: []const ast.StructTypeParam, type_bindings: std.StringHashMap(Type), val_bindings: ?std.StringHashMap(i64), comptime_nodes: ?std.StringHashMap(*Node), ) ![]const u8 { var buf = std.ArrayList(u8).empty; try buf.appendSlice(self.allocator, base); try buf.appendSlice(self.allocator, "__"); for (type_params, 0..) |tp, i| { if (i > 0) try buf.append(self.allocator, '_'); const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; if (std.mem.eql(u8, constraint_name, "Type")) { if (type_bindings.get(tp.name)) |ty| { const name = try ty.displayName(self.allocator); try buf.appendSlice(self.allocator, name); } } else if (comptime_nodes != null) { if (comptime_nodes.?.get(tp.name)) |node| { if (node.data == .string_literal) { const hash = std.hash.Wyhash.hash(0, node.data.string_literal.raw); var hash_buf: [16]u8 = undefined; const hash_str = std.fmt.bufPrint(&hash_buf, "{x}", .{hash}) catch "0"; try buf.appendSlice(self.allocator, hash_str); } else if (node.data == .int_literal) { var int_buf: [20]u8 = undefined; const int_str = std.fmt.bufPrint(&int_buf, "{d}", .{node.data.int_literal.value}) catch "0"; try buf.appendSlice(self.allocator, int_str); } } } else if (val_bindings != null) { if (val_bindings.?.get(tp.name)) |val| { var tmp: [20]u8 = undefined; const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; try buf.appendSlice(self.allocator, s); } } } return try buf.toOwnedSlice(self.allocator); } fn instantiateGeneric(self: *CodeGen, fd: ast.FnDecl, bindings: std.StringHashMap(Type), mangled: []const u8) !c.LLVMValueRef { // Save current codegen state const saved_function = self.current_function; const saved_return_type = self.current_return_type; const saved_insert_bb = self.getCurrentBlock(); // Save named_values var saved_named_values = std.StringHashMap(NamedValue).init(self.allocator); var nv_iter = self.named_values.iterator(); while (nv_iter.next()) |entry| { try saved_named_values.put(entry.key_ptr.*, entry.value_ptr.*); } // Save scope stack — generic body must not pollute caller's scope tracking const saved_scope_stack = self.scope_stack; self.scope_stack = std.ArrayList(Scope).empty; // Set type param bindings (save/restore to support nested generic instantiation) const saved_bindings = self.type_param_bindings; self.type_param_bindings = bindings; defer self.type_param_bindings = saved_bindings; // Build the specialized function type const fn_type = try self.buildFnType(fd.params, fd.return_type, mangled, false); const mangled_z = try self.allocator.dupeZ(u8, mangled); const function = c.LLVMAddFunction(self.module, mangled_z.ptr, fn_type); // Cache before generating body (in case of recursion) try self.generic_instances.put(mangled, function); // Generate body self.named_values.clearRetainingCapacity(); self.current_function = function; _ = self.appendBlock(function, "entry"); // Create allocas for parameters var llvm_param_idx: u32 = 0; for (fd.params) |param| { if (param.is_comptime) { // Comptime param: create a constant in named_values from the call-site value if (self.comptime_param_nodes) |cpn| { if (cpn.get(param.name)) |node| { if (node.data == .string_literal) { const slit = node.data.string_literal; const raw = slit.raw; const inner = if (!slit.is_raw and raw.len >= 2 and raw[0] == '"' and raw[raw.len - 1] == '"') raw[1 .. raw.len - 1] else raw; const content = if (slit.is_raw) inner else try unescape.unescapeString(self.allocator, inner); const str_val = self.buildConstStr(content); const param_name_z = try self.allocator.dupeZ(u8, param.name); const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), param_name_z.ptr); _ = c.LLVMBuildStore(self.builder, str_val, alloca); try self.named_values.put(param.name, .{ .ptr = alloca, .ty = .string_type }); } else if (node.data == .int_literal) { const ct_sx_ty = self.resolveType(param.type_expr); const ct_llvm_ty = self.typeToLLVM(ct_sx_ty); const const_val = c.LLVMConstInt(ct_llvm_ty, @bitCast(node.data.int_literal.value), 0); const param_name_z = try self.allocator.dupeZ(u8, param.name); const alloca = c.LLVMBuildAlloca(self.builder, ct_llvm_ty, param_name_z.ptr); _ = c.LLVMBuildStore(self.builder, const_val, alloca); try self.named_values.put(param.name, .{ .ptr = alloca, .ty = ct_sx_ty }); } } } continue; } // Variadic params: use slice_type (same as genFnBody) const sx_ty = if (param.is_variadic) blk: { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; break :blk Type{ .slice_type = .{ .element_name = elem_name } }; } else self.resolveType(param.type_expr); try self.bindParam(function, param.name, sx_ty, llvm_param_idx); llvm_param_idx += 1; } // Generate body statements const body = fd.body; if (body.data != .block) return self.emitError("generic function body must be a block"); const ret_sx_type = self.resolveType(fd.return_type); self.current_return_type = ret_sx_type; var last_val: c.LLVMValueRef = null; for (body.data.block.stmts) |stmt| { last_val = try self.genStmt(stmt); } // Emit return if current block has no terminator const current_bb = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { if (ret_sx_type == .void_type) { self.retVoid(); } else if (last_val) |val| { if (ret_sx_type.isStruct()) { const sname = ret_sx_type.struct_type; const info = try self.getStructInfo(sname); const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, val, "retval"); self.ret(loaded); } else { const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); const converted = self.convertValue(val, src_ty, ret_sx_type); self.ret(converted); } } else { _ = c.LLVMBuildUnreachable(self.builder); } } // Restore codegen state self.current_function = saved_function; self.current_return_type = saved_return_type; if (saved_insert_bb) |bb| { self.positionAt(bb); } self.named_values.clearRetainingCapacity(); var restore_iter = saved_named_values.iterator(); while (restore_iter.next()) |entry| { try self.named_values.put(entry.key_ptr.*, entry.value_ptr.*); } saved_named_values.deinit(); // Restore scope stack self.scope_stack = saved_scope_stack; return function; } fn genIfExpr(self: *CodeGen, if_expr: ast.IfExpr) !c.LLVMValueRef { // Generate condition const cond_val = self.valueToBool(try self.genExpr(if_expr.condition)); const has_else = if_expr.else_branch != null; var then_bb = self.appendBB("then"); var else_bb: c.LLVMBasicBlockRef = if (has_else) self.appendBB("else") else null; const merge_bb = self.appendBB("merge"); const false_dest = if (has_else) else_bb else merge_bb; self.condBr(cond_val, then_bb, false_dest); // Then branch self.positionAt(then_bb); const then_val = try self.genExpr(if_expr.then_branch); then_bb = self.getCurrentBlock(); // may have changed due to nested control flow self.br(merge_bb); // Else branch var else_val: c.LLVMValueRef = null; if (if_expr.else_branch) |else_branch| { self.positionAt(else_bb); else_val = try self.genExpr(else_branch); else_bb = self.getCurrentBlock(); self.br(merge_bb); } // Merge block self.positionAt(merge_bb); // PHI node if both branches produced values (skip for void type) if (then_val != null and else_val != null) { const ty = c.LLVMTypeOf(then_val); if (c.LLVMGetTypeKind(ty) != c.LLVMVoidTypeKind) { const phi = c.LLVMBuildPhi(self.builder, ty, "iftmp"); var vals = [2]c.LLVMValueRef{ then_val, else_val }; var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb }; c.LLVMAddIncoming(phi, &vals, &blocks, 2); return phi; } } return null; } fn genWhileExpr(self: *CodeGen, while_expr: ast.WhileExpr) !c.LLVMValueRef { // Create basic blocks: condition, body, after const cond_bb = self.appendBB("while.cond"); const body_bb = self.appendBB("while.body"); const after_bb = self.appendBB("while.after"); // Branch from current block to condition check self.br(cond_bb); // Condition block self.positionAt(cond_bb); const cond_val = self.valueToBool(try self.genExpr(while_expr.condition)); self.condBr(cond_val, body_bb, after_bb); // Body block — save and set loop context for break/continue self.positionAt(body_bb); const saved_break_bb = self.loop_break_bb; const saved_continue_bb = self.loop_continue_bb; self.loop_break_bb = after_bb; self.loop_continue_bb = cond_bb; _ = try self.genExpr(while_expr.body); // Restore loop context self.loop_break_bb = saved_break_bb; self.loop_continue_bb = saved_continue_bb; // Branch back to condition (if not already terminated by break/return) const current_bb = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { self.br(cond_bb); } // Position at after block self.positionAt(after_bb); return null; } fn genForExpr(self: *CodeGen, for_expr: ast.ForExpr) !c.LLVMValueRef { const i64_type = self.i64Type(); // Determine iterable type and get length + element access info const iter_ty = self.inferType(for_expr.iterable); var len_val: c.LLVMValueRef = undefined; var elem_ty: Type = Type.s(64); var iter_ptr: c.LLVMValueRef = undefined; // pointer to data var is_slice = false; if (iter_ty.isSlice()) { is_slice = true; const info = iter_ty.slice_type; elem_ty = Type.fromName(info.element_name) orelse Type.s(64); // Load slice value from alloca if (for_expr.iterable.data == .identifier) { if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "for_slice"); iter_ptr = self.extractValue(slice_val, 0, "for_ptr"); len_val = self.extractValue(slice_val, 1, "for_len"); } else return self.emitError("for: iterable not found"); } else return self.emitError("for: slice iterable must be a variable"); } else if (iter_ty.isArray()) { const info = iter_ty.array_type; elem_ty = Type.fromName(info.element_name) orelse Type.s(64); len_val = c.LLVMConstInt(i64_type, info.length, 0); // Get pointer to array if (for_expr.iterable.data == .identifier) { if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { iter_ptr = entry.ptr; } else return self.emitError("for: iterable not found"); } else return self.emitError("for: array iterable must be a variable"); } else { return self.emitError("for loop requires a slice or array iterable"); } const elem_llvm_ty = self.typeToLLVM(elem_ty); // Allocate index variable const idx_alloca = self.buildEntryBlockAlloca(i64_type, "for_idx"); _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_type, 0, 0), idx_alloca); // Push scope and bind index if requested try self.pushScope(); if (for_expr.index_name) |idx_name| { if (!std.mem.eql(u8, idx_name, "_")) { try self.named_values.put(idx_name, .{ .ptr = idx_alloca, .ty = Type.s(64), .is_const = true }); } } // Create basic blocks const cond_bb = self.appendBB("for.cond"); const body_bb = self.appendBB("for.body"); const incr_bb = self.appendBB("for.incr"); const after_bb = self.appendBB("for.after"); self.br(cond_bb); // Condition: index < len self.positionAt(cond_bb); const cur_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "cur_idx"); const cond_val = self.icmp(c.LLVMIntSLT, cur_idx, len_val, "for_cond"); self.condBr(cond_val, body_bb, after_bb); // Body: compute element GEP, bind capture, execute body self.positionAt(body_bb); const body_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "body_idx"); const elem_gep = if (is_slice) self.gepPointerElement(elem_llvm_ty, iter_ptr, body_idx, "for_elem") else blk: { const arr_llvm_ty = self.typeToLLVM(iter_ty); const zero = c.LLVMConstInt(i64_type, 0, 0); var indices = [_]c.LLVMValueRef{ zero, body_idx }; break :blk c.LLVMBuildGEP2(self.builder, arr_llvm_ty, iter_ptr, &indices, 2, "for_elem"); }; if (!std.mem.eql(u8, for_expr.capture_name, "_")) { // Alias mode: capture points directly to element in array/slice try self.named_values.put(for_expr.capture_name, .{ .ptr = elem_gep, .ty = elem_ty, .is_const = true, }); } // Save and set loop context for break/continue const saved_break_bb = self.loop_break_bb; const saved_continue_bb = self.loop_continue_bb; self.loop_break_bb = after_bb; self.loop_continue_bb = incr_bb; _ = try self.genExpr(for_expr.body); self.loop_break_bb = saved_break_bb; self.loop_continue_bb = saved_continue_bb; // Fall through to increment block const current_bb = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { self.br(incr_bb); } // Increment index, then branch back to condition self.positionAt(incr_bb); const inc_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "inc_idx"); const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i64_type, 1, 0), "next_idx"); _ = c.LLVMBuildStore(self.builder, next_idx, idx_alloca); self.br(cond_bb); self.positionAt(after_bb); try self.popScope(); return null; } fn genEnumLiteral(self: *CodeGen, variant_name: []const u8, enum_type_name: []const u8) c.LLVMValueRef { const enum_ty = self.getEnumLLVMType(enum_type_name); const variants = self.lookupEnumVariants(enum_type_name) orelse return c.LLVMConstInt(enum_ty, 0, 0); const values = self.enum_variant_values.get(enum_type_name); for (variants, 0..) |v, i| { if (std.mem.eql(u8, v, variant_name)) { const val: u64 = if (values) |vals| @bitCast(vals[i]) else @intCast(i); return c.LLVMConstInt(enum_ty, val, 0); } } return c.LLVMConstInt(enum_ty, 0, 0); } fn lookupVariantValue(self: *CodeGen, enum_name: ?[]const u8, variants: ?[]const []const u8, name: []const u8) u64 { if (variants) |vs| { for (vs, 0..) |v, i| { if (std.mem.eql(u8, v, name)) { // Use resolved values if available (flags enums, explicit values) if (enum_name) |en| { if (self.enum_variant_values.get(en)) |vals| { return @bitCast(vals[i]); } } return i; } } } return 0; } fn genMatchExpr(self: *CodeGen, match: ast.MatchExpr) !c.LLVMValueRef { // Determine subject type for enum vs union dispatch var enum_name: ?[]const u8 = null; var union_name: ?[]const u8 = null; const subject_ty = self.inferType(match.subject); if (subject_ty.isEnum()) enum_name = subject_ty.enum_type; if (subject_ty.isUnion()) union_name = subject_ty.union_type; // Get the switch value: for unions, load the tag from field 0; for enums, use the value directly const subject_val: c.LLVMValueRef = if (union_name != null) blk: { // Union: load tag from field 0 of the alloca const entry = self.named_values.get(match.subject.data.identifier.name).?; const info = self.lookupTaggedEnumInfo(union_name.?).?; break :blk self.loadStructField(info.llvm_type, entry.ptr, 0, self.getEnumLLVMType(union_name.?)); } else try self.genExpr(match.subject); const variants: ?[]const []const u8 = if (union_name) |un| (if (self.lookupTaggedEnumInfo(un)) |info| info.variant_names else null) else if (enum_name) |en| self.lookupEnumVariants(en) else null; const i64_type = self.i64Type(); // Enum/union case constants use the backing type; Any dispatch uses i64 const case_int_type = if (enum_name) |en| self.getEnumLLVMType(en) else if (union_name) |un| self.getEnumLLVMType(un) else i64_type; const merge_bb = self.appendBB("match_end"); // Create case basic blocks var case_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; for (match.arms) |_| { try case_bbs.append(self.allocator, self.appendBB("case")); } // Find else arm (null pattern) — use its BB as the switch default var else_arm_idx: ?usize = null; for (match.arms, 0..) |arm, i| { if (arm.pattern == null) { else_arm_idx = i; break; } } const default_bb = if (else_arm_idx) |idx| case_bbs.items[idx] else self.appendBB("match_default"); // Build switch instruction const sw = c.LLVMBuildSwitch(self.builder, subject_val, default_bb, @intCast(match.arms.len)); for (match.arms, 0..) |arm, i| { const pat = arm.pattern orelse continue; // skip else arm if (pat.data == .enum_literal) { const idx = self.lookupVariantValue(enum_name orelse union_name, variants, pat.data.enum_literal.name); const case_val = c.LLVMConstInt(case_int_type, idx, 0); c.LLVMAddCase(sw, case_val, case_bbs.items[i]); } else if (pat.data == .type_expr) { // Type-match: resolve type name to Any tag value(s) const tag_values = try self.resolveTypeMatchTags(pat.data.type_expr.name); for (tag_values) |tag| { c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]); } } else if (pat.data == .identifier) { // Named type (struct/enum/union name) or category (int/float) const tag_values = try self.resolveTypeMatchTags(pat.data.identifier.name); for (tag_values) |tag| { c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]); } } } // Generate arm bodies and collect PHI info var phi_vals = std.ArrayList(c.LLVMValueRef).empty; var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; var has_value = false; var value_type: c.LLVMTypeRef = null; // Pre-collect tag values for each arm (for runtime dispatch context) var arm_tag_values = std.ArrayList([]const u64).empty; for (match.arms) |arm| { const tag_values: []const u64 = if (arm.pattern) |pat| blk: { break :blk if (pat.data == .type_expr) try self.resolveTypeMatchTags(pat.data.type_expr.name) else if (pat.data == .identifier) try self.resolveTypeMatchTags(pat.data.identifier.name) else &.{}; } else &.{}; try arm_tag_values.append(self.allocator, tag_values); } for (match.arms, 0..) |arm, i| { self.positionAt(case_bbs.items[i]); if (arm.is_break) { self.br(merge_bb); } else if (arm.pattern != null and arm_tag_values.items[i].len == 0 and (arm.pattern.?.data == .identifier or arm.pattern.?.data == .type_expr)) { // Category/type arm with no matching types — BB is unreachable, skip body self.br(merge_bb); } else { // Payload capture: bind variant payload as a local variable if (arm.capture) |cap_name| { if (union_name) |un| { const uinfo = self.lookupTaggedEnumInfo(un).?; const pat = arm.pattern.?; if (pat.data == .enum_literal) { const vname = pat.data.enum_literal.name; var vidx: ?usize = null; for (uinfo.variant_names, 0..) |vn, vi| { if (std.mem.eql(u8, vn, vname)) { vidx = vi; break; } } if (vidx) |vi| { const variant_ty = uinfo.variant_types[vi]; if (variant_ty != .void_type) { const subject_entry = self.named_values.get(match.subject.data.identifier.name).?; const payload_gep = self.structGEP(uinfo.llvm_type, subject_entry.ptr, uinfo.payload_field_index, "cap_payload"); const payload_llvm_ty = self.typeToLLVM(variant_ty); const payload_val = c.LLVMBuildLoad2(self.builder, payload_llvm_ty, payload_gep, "cap_load"); const cap_alloca = c.LLVMBuildAlloca(self.builder, payload_llvm_ty, @ptrCast(cap_name.ptr)); _ = c.LLVMBuildStore(self.builder, payload_val, cap_alloca); try self.named_values.put(cap_name, .{ .ptr = cap_alloca, .ty = variant_ty }); } } } } } // Set match arm context for runtime type dispatch const saved_match_tags = self.current_match_tags; self.current_match_tags = arm_tag_values.items[i]; const val = try self.genExpr(arm.body); self.current_match_tags = saved_match_tags; const bb = self.getCurrentBlock(); self.br(merge_bb); if (val != null and c.LLVMGetTypeKind(c.LLVMTypeOf(val)) != c.LLVMVoidTypeKind) { has_value = true; if (value_type == null) value_type = c.LLVMTypeOf(val); try phi_vals.append(self.allocator, val); try phi_bbs.append(self.allocator, bb); } } } // Default block branches to merge (only if no else arm — else arm's body already generated above) if (else_arm_idx == null) { self.positionAt(default_bb); self.br(merge_bb); } // Merge block self.positionAt(merge_bb); if (has_value and value_type != null) { const undef_val = self.getUndef(value_type); // Add undef entries for break arms and default block for (match.arms, 0..) |arm, i| { if (arm.is_break) { try phi_vals.append(self.allocator, undef_val); try phi_bbs.append(self.allocator, case_bbs.items[i]); } } if (else_arm_idx == null) { try phi_vals.append(self.allocator, undef_val); try phi_bbs.append(self.allocator, default_bb); } const phi = try self.buildPhiNode(&phi_vals, &phi_bbs, value_type, "matchtmp"); return phi; } return null; } /// Resolve a type name to one or more Any tag values for type-switch matching. /// Categories: "int" matches s32+s64, "float" matches f32+f64. /// Specific types: "s32", "f64", "string", "bool", "Type". /// Named types: struct/enum/union names get dynamic IDs. fn resolveTypeMatchTags(self: *CodeGen, name: []const u8) ![]const u64 { // Category aliases if (std.mem.eql(u8, name, "int")) { const tags = try self.allocator.alloc(u64, 2); tags[0] = ANY_TAG_S32; tags[1] = ANY_TAG_S64; return tags; } if (std.mem.eql(u8, name, "float")) { const tags = try self.allocator.alloc(u64, 2); tags[0] = ANY_TAG_F32; tags[1] = ANY_TAG_F64; return tags; } // Type category aliases: "struct", "enum", "union", "vector", "array", "slice" const category: ?TypeCategory = if (std.mem.eql(u8, name, "struct")) .struct_cat else if (std.mem.eql(u8, name, "enum")) .enum_cat else if (std.mem.eql(u8, name, "union")) .enum_cat else if (std.mem.eql(u8, name, "vector")) .vector_cat else if (std.mem.eql(u8, name, "array")) .array_cat else if (std.mem.eql(u8, name, "slice")) .slice_cat else if (std.mem.eql(u8, name, "pointer")) .pointer_cat else null; if (category) |cat| { var tag_list = std.ArrayList(u64).empty; var it = self.any_type_entries.iterator(); while (it.next()) |entry| { if (entry.value_ptr.category == cat) { try tag_list.append(self.allocator, entry.value_ptr.tag_id); } } if (tag_list.items.len > 0) { return try tag_list.toOwnedSlice(self.allocator); } // No types registered for this category — return empty slice return &.{}; } // Specific builtin types const single_tag: ?u64 = if (std.mem.eql(u8, name, "bool")) ANY_TAG_BOOL else if (std.mem.eql(u8, name, "s32")) ANY_TAG_S32 else if (std.mem.eql(u8, name, "s64")) ANY_TAG_S64 else if (std.mem.eql(u8, name, "f32")) ANY_TAG_F32 else if (std.mem.eql(u8, name, "f64")) ANY_TAG_F64 else if (std.mem.eql(u8, name, "string")) ANY_TAG_STRING else if (std.mem.eql(u8, name, "Type") or std.mem.eql(u8, name, "type")) ANY_TAG_TYPE else if (std.mem.eql(u8, name, "void")) ANY_TAG_VOID else null; if (single_tag) |t| { const tags = try self.allocator.alloc(u64, 1); tags[0] = t; return tags; } // Named type (struct/enum/union) — get dynamic ID const sx_type: Type = if (self.type_registry.get(name)) |e| switch (e) { .struct_info => Type{ .struct_type = name }, .plain_enum => Type{ .enum_type = name }, .tagged_enum => Type{ .union_type = name }, .union_info => Type{ .union_type = name }, .alias => Type{ .struct_type = name }, } else .{ .struct_type = name }; // fallback const id = try self.getAnyTypeId(name, sx_type); const tags = try self.allocator.alloc(u64, 1); tags[0] = id; return tags; } /// Resolve a callee node to a function name string for type inference. /// Handles identifiers, namespaced calls, and intra-namespace fallback. fn resolveCalleeName(self: *CodeGen, call_node: ast.Call) ?[]const u8 { if (call_node.callee.data == .identifier) { return call_node.callee.data.identifier.name; } if (call_node.callee.data == .field_access) { const fa = call_node.callee.data.field_access; if (fa.object.data == .identifier) { if (self.namespaces.contains(fa.object.data.identifier.name)) { return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch return null; } } } return null; } /// Resolve a builtin parameterized type (e.g. Vector(3, f32)). /// Strips namespace prefix to get base name, then dispatches. fn resolveBuiltinType(self: *CodeGen, name: []const u8, args: []const *Node) ?Type { const base = baseName(name); if (std.mem.eql(u8, base, "Vector")) { if (args.len >= 2) { const n: u32 = @intCast(self.resolveValueArg(args[0])); const elem = self.resolveType(args[1]); const elem_name = elem.displayName(self.allocator) catch return null; const ty: Type = .{ .vector_type = .{ .element_name = elem_name, .length = n } }; // Pre-register in any_type_entries so runtime dispatch knows about this type const any_name = std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ n, elem_name }) catch return null; _ = self.getAnyTypeId(any_name, ty) catch return null; return ty; } } if (std.mem.eql(u8, base, "Array")) { if (args.len >= 2) { const n: u32 = @intCast(self.resolveValueArg(args[0])); const elem = self.resolveType(args[1]); const elem_name = elem.displayName(self.allocator) catch return null; const ty: Type = .{ .array_type = .{ .element_name = elem_name, .length = n } }; const any_name = std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ n, elem_name }) catch return null; _ = self.getAnyTypeId(any_name, ty) catch return null; return ty; } } return null; } fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef { // Extract base name (strip namespace prefix) const base = baseName(name); if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args); if (std.mem.eql(u8, base, "sqrt")) return self.genMathIntrinsic(call_node, "sqrt"); if (std.mem.eql(u8, base, "sin")) return self.genMathIntrinsic(call_node, "sin"); if (std.mem.eql(u8, base, "cos")) return self.genMathIntrinsic(call_node, "cos"); if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node); if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node); if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args); if (std.mem.eql(u8, base, "malloc")) return self.genMalloc(call_node.args); if (std.mem.eql(u8, base, "free")) return self.genFree(call_node.args); if (std.mem.eql(u8, base, "memcpy")) return self.genMemcpy(call_node.args); if (std.mem.eql(u8, base, "type_of")) return self.genTypeOf(call_node); if (std.mem.eql(u8, base, "type_name")) return self.genTypeName(call_node); if (std.mem.eql(u8, base, "field_count")) return self.genFieldCount(call_node); if (std.mem.eql(u8, base, "field_name")) return self.genFieldName(call_node); if (std.mem.eql(u8, base, "field_value")) return self.genFieldValue(call_node); if (std.mem.eql(u8, base, "is_flags")) return self.genIsFlags(call_node); if (std.mem.eql(u8, base, "field_value_int")) return self.genFieldValueInt(call_node); if (std.mem.eql(u8, base, "field_index")) return self.genFieldIndex(call_node); return self.emitErrorFmt("unknown builtin function '{s}'", .{name}); } fn genWriteCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { if (args.len != 1) return self.emitError("write expects exactly 1 argument"); const builtins = try self.requireBuiltins(); const val = try self.genExpr(args[0]); // Extract ptr and len from string slice const ptr = self.extractValue(val, 0, "str_ptr"); const len_i64 = self.extractValue(val, 1, "str_len"); // printf %.*s precision is C int (i32) — truncate from i64 const len = self.trunc(len_i64, self.i32Type(), "len_trunc"); const fmt = self.buildGlobalString("%.*s", "write_fmt"); const printf_fn = builtins.printf_fn; const fn_type = c.LLVMGlobalGetValueType(printf_fn); var call_args = [_]c.LLVMValueRef{ fmt, len, ptr }; _ = c.LLVMBuildCall2(self.builder, fn_type, printf_fn, &call_args, 3, ""); return null; } /// Helper: build a constant string slice in the current function fn buildConstStr(self: *CodeGen, s: []const u8) c.LLVMValueRef { const sz = self.allocator.dupeZ(u8, s) catch unreachable; const ptr = self.buildGlobalString(sz.ptr, "cstr"); return self.buildStringSlice(ptr, self.constInt64(@intCast(s.len))); } /// Helper: build a constant string slice as a global constant (no builder needed). fn buildConstStrGlobal(self: *CodeGen, s: []const u8) c.LLVMValueRef { const sz = self.allocator.dupeZ(u8, s) catch unreachable; const i64_ty = self.i64Type(); const i8_ty = self.i8Type(); // Create a global string constant const str_const = c.LLVMConstStringInContext(self.context, sz.ptr, @intCast(s.len), 0); const global_name = (self.allocator.dupeZ(u8, std.fmt.allocPrint(self.allocator, ".str.{s}", .{s}) catch unreachable)) catch unreachable; var global = c.LLVMGetNamedGlobal(self.module, global_name.ptr); if (global == null) { const arr_ty = c.LLVMArrayType2(i8_ty, s.len + 1); global = c.LLVMAddGlobal(self.module, arr_ty, global_name.ptr); c.LLVMSetInitializer(global, str_const); c.LLVMSetGlobalConstant(global, 1); c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); } // Build constant struct {ptr, i64} var fields = [_]c.LLVMValueRef{ c.LLVMConstBitCast(global.?, self.ptrType()), c.LLVMConstInt(i64_ty, s.len, 0), }; return c.LLVMConstStructInContext(self.context, &fields, 2, 0); } /// Check if a node refers to a type name. Returns the raw name or null. fn asTypeName(self: *CodeGen, node: *const Node) ?[]const u8 { if (node.data == .type_expr) return node.data.type_expr.name; if (node.data == .identifier) { const id = node.data.identifier.name; if (self.resolveTypeName(id) != null) return id; } return null; } /// Resolve a type name to its display string (null-terminated) for runtime use. fn resolveDisplayName(self: *CodeGen, name: []const u8) [:0]const u8 { const display = self.resolveTypeName(name) orelse name; return self.allocator.dupeZ(u8, display) catch unreachable; } /// Convert a type expression AST node to its source string representation. fn typeNodeToString(self: *CodeGen, node: *const Node) []const u8 { return switch (node.data) { .type_expr => |te| te.name, .identifier => |id| id.name, .pointer_type_expr => |pte| std.fmt.allocPrint(self.allocator, "*{s}", .{self.typeNodeToString(pte.pointee_type)}) catch "?", .many_pointer_type_expr => |mpte| std.fmt.allocPrint(self.allocator, "[*]{s}", .{self.typeNodeToString(mpte.element_type)}) catch "?", .slice_type_expr => |ste| std.fmt.allocPrint(self.allocator, "[]{s}", .{self.typeNodeToString(ste.element_type)}) catch "?", .array_type_expr => |ate| blk: { const elem = self.typeNodeToString(ate.element_type); if (ate.length.data == .int_literal) { break :blk std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ ate.length.data.int_literal.value, elem }) catch "?"; } break :blk std.fmt.allocPrint(self.allocator, "[?]{s}", .{elem}) catch "?"; }, else => "?", }; } /// Build a function type string like "() -> s32" from an fn_decl. fn buildFnSignature(self: *CodeGen, fd: ast.FnDecl) []const u8 { var buf = std.ArrayList(u8).empty; buf.appendSlice(self.allocator, "(") catch return "?"; for (fd.params, 0..) |param, i| { if (i > 0) buf.appendSlice(self.allocator, ", ") catch {}; if (param.is_variadic) buf.appendSlice(self.allocator, "..") catch {}; const ty_str = self.typeNodeToString(param.type_expr); buf.appendSlice(self.allocator, ty_str) catch {}; } buf.appendSlice(self.allocator, ")") catch {}; if (fd.return_type) |rt| { buf.appendSlice(self.allocator, " -> ") catch {}; buf.appendSlice(self.allocator, self.typeNodeToString(rt)) catch {}; } return buf.toOwnedSlice(self.allocator) catch "?"; } /// Extract a qualified name from a callee expression (identifier or field_access chain). fn calleeToQualifiedName(self: *CodeGen, callee: *Node) ?[]const u8 { if (callee.data == .identifier) return callee.data.identifier.name; if (callee.data == .field_access) { const obj_name = self.calleeToQualifiedName(callee.data.field_access.object) orelse return null; return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ obj_name, callee.data.field_access.field }) catch null; } return null; } /// Resolve a name to a type display string, or null if not a type. fn resolveTypeName(self: *CodeGen, name: []const u8) ?[]const u8 { const resolved = self.resolveAlias(name); if (self.lookupStructInfo(resolved)) |info| return info.display_name orelse resolved; if (self.resolveTypeFromName(name) != null) return resolved; return null; } /// Resolve a type name to a Type, checking primitives + registered structs/unions/enums. /// Unlike Type.fromName which only handles primitives. fn resolveTypeFromName(self: *CodeGen, name: []const u8) ?Type { // Primitives if (Type.fromName(name)) |t| return t; // Unified type registry lookup if (self.type_registry.get(name)) |entry| switch (entry) { .struct_info => return .{ .struct_type = name }, .tagged_enum => return .{ .union_type = name }, .union_info => return .{ .union_type = name }, .plain_enum => return .{ .enum_type = name }, .alias => |target| { if (Type.fromName(target)) |t| return t; if (self.type_registry.get(target)) |inner| switch (inner) { .struct_info => return .{ .struct_type = target }, .tagged_enum => return .{ .union_type = target }, .union_info => return .{ .union_type = target }, .plain_enum => return .{ .enum_type = target }, .alias => {}, }; }, }; // Vector display name: "Vector(N,T)" if (name.len > 8 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') { const inner = name[7 .. name.len - 1]; // "N,T" if (std.mem.indexOfScalar(u8, inner, ',')) |comma| { const n_str = inner[0..comma]; const elem = inner[comma + 1 ..]; const length = std.fmt.parseInt(u32, n_str, 10) catch return null; return .{ .vector_type = .{ .element_name = elem, .length = length } }; } } // Type aliases if (self.lookupAlias(name)) |target| return self.resolveTypeFromName(target); return null; } fn inferType(self: *CodeGen, node: *Node) Type { return switch (node.data) { .int_literal => Type.s(64), .float_literal => .f32, .bool_literal => .boolean, .string_literal => .string_type, .insert_expr => .void_type, .comptime_expr => |ct| self.inferType(ct.expr), .binary_op => |binop| { switch (binop.op) { .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => return .boolean, else => { const lhs_ty = self.inferType(binop.lhs); const rhs_ty = self.inferType(binop.rhs); return Type.widen(lhs_ty, rhs_ty); }, } }, .chained_comparison => return .boolean, .identifier => |ident| { if (self.lookupValue(ident.name)) |v| return v.ty(); return Type.s(64); }, .if_expr => |ie| { return self.inferType(ie.then_branch); }, .block => |blk| { if (blk.stmts.len > 0) { return self.inferType(blk.stmts[blk.stmts.len - 1]); } return .void_type; }, .enum_literal => { if (self.current_return_type.isEnum()) return self.current_return_type; if (self.current_return_type.isUnion()) return self.current_return_type; return .{ .enum_type = "" }; }, .match_expr => |me| { for (me.arms) |arm| { if (!arm.is_break) return self.inferType(arm.body); } return .void_type; }, .call => |call_node| { // Check for union literal pattern: Type.variant(payload) if (call_node.callee.data == .field_access) { const fa = call_node.callee.data.field_access; const obj_ty = blk: { if (fa.object.data == .identifier) { const name = self.resolveAlias(fa.object.data.identifier.name); if (self.lookupTaggedEnumInfo(name) != null) break :blk Type{ .union_type = name }; } const ty = self.resolveType(fa.object); if (ty.isUnion()) break :blk ty; break :blk @as(?Type, null); }; if (obj_ty) |uty| return uty; } const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64); const base_name = baseName(callee_name); // Built-in: sqrt/sin/cos returns same type as argument if (std.mem.eql(u8, base_name, "sqrt") or std.mem.eql(u8, base_name, "sin") or std.mem.eql(u8, base_name, "cos")) { if (call_node.args.len > 0) return self.inferType(call_node.args[0]); return .f32; } // Built-in: size_of returns s64 if (std.mem.eql(u8, base_name, "size_of")) return Type.s(64); // Built-in: type_of returns s64 (type tag) if (std.mem.eql(u8, base_name, "type_of")) return Type.s(64); // Built-in: type_name returns string if (std.mem.eql(u8, base_name, "type_name")) return .string_type; // Built-in: field_count returns s64 if (std.mem.eql(u8, base_name, "field_count")) return Type.s(64); // Built-in: field_name returns string if (std.mem.eql(u8, base_name, "field_name")) return .string_type; // Built-in: field_value returns Any if (std.mem.eql(u8, base_name, "field_value")) return .{ .any_type = {} }; // Built-in: is_flags returns bool if (std.mem.eql(u8, base_name, "is_flags")) return .boolean; // Built-in: field_value_int returns s64 if (std.mem.eql(u8, base_name, "field_value_int")) return Type.s(64); // Built-in: field_index returns s64 if (std.mem.eql(u8, base_name, "field_index")) return Type.s(64); // Built-in: cast returns the target type (first arg) if (std.mem.eql(u8, base_name, "cast")) { if (call_node.args.len > 0) return self.resolveType(call_node.args[0]); return Type.s(64); } // Built-in: alloc returns string if (std.mem.eql(u8, base_name, "alloc")) return .string_type; if (std.mem.eql(u8, base_name, "malloc")) return .{ .pointer_type = .{ .pointee_name = "void" } }; if (std.mem.eql(u8, base_name, "free")) return .void_type; if (std.mem.eql(u8, base_name, "memcpy")) return .void_type; // Check generic templates — infer return type from widened bindings const template = self.generic_templates.get(callee_name) orelse blk: { // Intra-namespace fallback if (self.current_namespace) |ns| { const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); break :blk self.generic_templates.get(qualified); } break :blk null; }; if (template) |tmpl| { const gfd = tmpl; // Build widened type bindings from all call args var inferred_bindings = std.StringHashMap(Type).init(self.allocator); for (gfd.params, 0..) |param, pi| { if (param.type_expr.data == .type_expr) { for (gfd.type_params) |tp| { if (std.mem.eql(u8, tp.name, param.type_expr.data.type_expr.name)) { if (pi < call_node.args.len) { const arg_ty = self.inferType(call_node.args[pi]); if (inferred_bindings.get(tp.name)) |existing| { inferred_bindings.put(tp.name, Type.widen(existing, arg_ty)) catch {}; } else { inferred_bindings.put(tp.name, arg_ty) catch {}; } } break; } } } } // Resolve return type from bindings if (gfd.return_type) |rt| { if (rt.data == .type_expr) { if (inferred_bindings.get(rt.data.type_expr.name)) |bound_ty| { return bound_ty; } } // Try resolving as a concrete type (e.g. -> string, -> s32) const resolved = self.resolveType(rt); if (!std.meta.eql(resolved, Type.void_type)) return resolved; } return Type.s(64); } // Check declared return types (preserves signedness) if (self.function_return_types.get(callee_name)) |ret_ty| return ret_ty; if (self.current_namespace) |ns| { const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); if (self.function_return_types.get(qualified)) |ret_ty| return ret_ty; } // Fallback: check non-generic LLVM functions var cnbuf2: [256]u8 = undefined; var callee_fn_opt = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf2)); // Intra-namespace fallback if (callee_fn_opt == null) { if (self.current_namespace) |ns2| { const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns2, callee_name }) catch return Type.s(64); var qbuf2: [256]u8 = undefined; callee_fn_opt = c.LLVMGetNamedFunction(self.module, self.nameToCStr(q, &qbuf2)); } } if (callee_fn_opt) |callee_fn| { const fn_type = c.LLVMGlobalGetValueType(callee_fn); const ret_llvm = c.LLVMGetReturnType(fn_type); return self.llvmTypeToSxType(ret_llvm); } // Check if callee is a variable with function pointer type { if (self.lookupValue(callee_name)) |v| { if (v.ty().isFunctionType()) { return v.ty().function_type.return_type.*; } } } return Type.s(64); }, .unary_op => |unop| { if (unop.op == .address_of) { const operand_ty = self.inferType(unop.operand); const name = operand_ty.displayName(self.allocator) catch return Type.s(64); return .{ .pointer_type = .{ .pointee_name = name } }; } return self.inferType(unop.operand); }, .deref_expr => |de| { const ptr_ty = self.inferType(de.operand); if (ptr_ty.isPointer()) return self.resolveTypeFromName(ptr_ty.pointer_type.pointee_name) orelse Type.s(64); return Type.s(64); }, .null_literal => return .{ .pointer_type = .{ .pointee_name = "void" } }, .field_access => |fa| { var obj_ty = self.inferType(fa.object); // Auto-deref: if pointer, unwrap to pointee if (obj_ty.isPointer()) { obj_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse Type.s(64); } if (obj_ty == .string_type) { if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); if (std.mem.eql(u8, fa.field, "ptr")) return .{ .pointer_type = .{ .pointee_name = "u8" } }; } if (obj_ty.isSlice()) { if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); } if (obj_ty.isArray()) { if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); } if (obj_ty.isAny()) { if (std.mem.eql(u8, fa.field, "tag")) return Type.s(64); if (std.mem.eql(u8, fa.field, "value")) return Type.s(64); } if (obj_ty.isVector()) { return obj_ty.vectorElementType() orelse Type.s(64); } if (obj_ty.isStruct()) { if (self.lookupStructInfo(obj_ty.struct_type)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |idx| { return info.field_types[idx]; } } } if (obj_ty.isUnion()) { if (self.lookupUnionInfo(obj_ty.union_type)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |idx| { return info.field_types[idx]; } if (info.promoted_fields.get(fa.field)) |pf| { return pf.field_type; } } if (self.lookupTaggedEnumInfo(obj_ty.union_type)) |info| { for (info.variant_names, 0..) |vn, i| { if (std.mem.eql(u8, vn, fa.field)) { return info.variant_types[i]; } } } } return Type.s(64); }, .index_expr => |ie| { const obj_ty = self.inferType(ie.object); if (obj_ty == .string_type) return Type.u(8); if (obj_ty.isVector()) { return obj_ty.vectorElementType() orelse Type.s(64); } if (obj_ty.isArray()) { return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(64); } if (obj_ty.isSlice()) { return obj_ty.sliceElementType() orelse Type.s(64); } if (obj_ty.isManyPointer()) { return self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse Type.s(64); } return Type.s(64); }, .slice_expr => |se| { const obj_ty = self.inferType(se.object); if (obj_ty == .string_type) return .{ .slice_type = .{ .element_name = "u8" } }; if (obj_ty.isArray()) return .{ .slice_type = .{ .element_name = obj_ty.array_type.element_name } }; if (obj_ty.isSlice()) return obj_ty; return .void_type; }, .array_literal => |al| { if (al.elements.len == 0) return .void_type; const elem_ty = self.inferType(al.elements[0]); const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(64); return .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; }, .while_expr, .for_expr, .break_expr, .continue_expr => .void_type, else => Type.s(64), }; } fn exprIsFloat(self: *CodeGen, node: *Node) bool { return self.inferType(node).isFloat(); } pub fn verify(self: *CodeGen) !void { var err_msg: [*c]u8 = null; if (c.LLVMVerifyModule(self.module, c.LLVMReturnStatusAction, &err_msg) != 0) { defer c.LLVMDisposeMessage(err_msg); const msg = std.mem.span(err_msg); return self.emitErrorFmt("LLVM verification failed: {s}", .{msg}); } } pub fn printIR(self: *CodeGen) void { const ir = c.LLVMPrintModuleToString(self.module); defer c.LLVMDisposeMessage(ir); const len = std.mem.len(ir); std.debug.print("{s}\n", .{ir[0..len]}); } fn emitToFile(self: *CodeGen, output_path: [*:0]const u8, file_type: c.LLVMCodeGenFileType) !void { const tm = self.target_machine orelse return self.emitError("no target machine available"); var err_msg: [*c]u8 = null; if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, file_type, &err_msg) != 0) { defer c.LLVMDisposeMessage(err_msg); const msg = std.mem.span(err_msg); return self.emitErrorFmt("failed to emit file: {s}", .{msg}); } } pub fn emitObject(self: *CodeGen, output_path: [*:0]const u8) !void { return self.emitToFile(output_path, c.LLVMObjectFile); } pub fn emitAssembly(self: *CodeGen, output_path: [*:0]const u8) !void { return self.emitToFile(output_path, c.LLVMAssemblyFile); } /// Emit the module as an object file to a memory buffer. /// Caller owns the returned buffer and must dispose or pass to JIT. pub fn emitObjectToMemory(self: *CodeGen) !c.LLVMMemoryBufferRef { const tm = self.target_machine orelse return self.emitError("no target machine available"); var err_msg: [*c]u8 = null; var buf: c.LLVMMemoryBufferRef = null; if (c.LLVMTargetMachineEmitToMemoryBuffer(tm, self.module, c.LLVMObjectFile, &err_msg, &buf) != 0) { if (err_msg != null) { defer c.LLVMDisposeMessage(err_msg); const msg = std.mem.span(err_msg); return self.emitErrorFmt("failed to emit object to memory: {s}", .{msg}); } return error.CompileError; } return buf; } /// Execute a precompiled object file in-process using LLVM's ORC JIT. /// Takes ownership of obj_buf. Returns the exit code from main(). pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 { // Create LLJIT with default builder (no custom TM needed — .o is precompiled) var jit: c.LLVMOrcLLJITRef = null; var err = c.LLVMOrcCreateLLJIT(&jit, null); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } defer _ = c.LLVMOrcDisposeLLJIT(jit); // Add process symbols so JIT can find libc (printf, write, etc.) const jd = c.LLVMOrcLLJITGetMainJITDylib(jit); const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit); var gen: c.LLVMOrcDefinitionGeneratorRef = null; err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT symbol gen error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } c.LLVMOrcJITDylibAddGenerator(jd, gen); // Add precompiled object file (transfers ownership of obj_buf) err = c.LLVMOrcLLJITAddObjectFile(jit, jd, obj_buf); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT add object error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } // Look up the "main" function var main_addr: c.LLVMOrcExecutorAddress = 0; err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main"); if (err != null) { const msg = c.LLVMGetErrorMessage(err); defer c.LLVMDisposeErrorMessage(msg); std.debug.print("JIT lookup error: {s}\n", .{std.mem.span(msg)}); return error.CompileError; } // Cast to function pointer and call const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr); const result = main_fn(); return if (result >= 0 and result <= 255) @intCast(result) else 1; } pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, output_bin: []const u8, libraries: []const []const u8, target_config: TargetConfig) !void { var argv = std.ArrayList([]const u8).empty; if (target_config.isWindows()) { // Windows: MSVC-style linker flags const linker = target_config.linker orelse "link.exe"; try argv.appendSlice(allocator, &.{ linker, output_obj }); try argv.append(allocator, try std.fmt.allocPrint(allocator, "/OUT:{s}", .{output_bin})); for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "/LIBPATH:{s}", .{lp})); } for (libraries) |lib| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "{s}.lib", .{lib})); } } else { // Unix: cc-style linker flags try argv.appendSlice(allocator, &.{ target_config.getLinker(), output_obj, "-o", output_bin }); if (target_config.sysroot) |sr| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr})); } // User-supplied library paths first for (target_config.lib_paths) |lp| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); } // Auto-detect host OS library paths when linking foreign libraries if (libraries.len > 0 and target_config.triple == null) { for (host_lib_paths) |path| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{path})); } } for (libraries) |lib| { try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib})); } } const argv_slice = try argv.toOwnedSlice(allocator); var child = std.process.spawn(io, .{ .argv = argv_slice, }) catch return error.LinkError; const result = child.wait(io) catch return error.LinkError; if (result != .exited) return error.LinkError; if (result.exited != 0) return error.LinkError; } /// Common library paths for the host OS, computed at comptime. const host_lib_paths = blk: { const builtin = @import("builtin"); var paths: []const []const u8 = &.{}; if (builtin.os.tag == .macos) { if (builtin.cpu.arch == .aarch64) { // Apple Silicon Homebrew paths = &.{ "/opt/homebrew/lib", "/usr/local/lib" }; } else { // Intel Mac Homebrew paths = &.{"/usr/local/lib"}; } } else if (builtin.os.tag == .linux) { paths = &.{ "/usr/local/lib", "/usr/lib" }; } break :blk paths; }; };