From f763765ea25385360c6fec3f94ff0281dedcd1f4 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 1 Mar 2026 22:38:41 +0200 Subject: [PATCH] ir done'ish --- examples/50-smoke.sx | 18 + src/codegen.zig | 11905 ---------------------------------- src/comptime.zig | 2752 -------- src/core.zig | 22 +- src/ir/emit_llvm.test.zig | 9 +- src/ir/emit_llvm.zig | 381 +- src/ir/inst.zig | 7 + src/ir/interp.zig | 17 + src/ir/lower.zig | 766 ++- src/ir/module.zig | 8 +- src/ir/print.zig | 1 + src/ir/type_bridge.zig | 53 +- src/ir/types.zig | 172 +- src/main.zig | 138 +- src/root.zig | 2 +- src/target.zig | 206 + tests/expected/50-smoke.txt | 3 + 17 files changed, 1443 insertions(+), 15017 deletions(-) delete mode 100644 src/codegen.zig delete mode 100644 src/comptime.zig create mode 100644 src/target.zig diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index 833e3af..b671a82 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -1782,6 +1782,24 @@ END; print("opt param 7: {}\n", opt_process(7)); // opt param 7: 7 } + // Assignment to optional variable (f32 → ?f32) + { + iw: ?f32 = null; + w: f32 = 42.5; + iw = w; + print("opt reassign: {}\n", iw ?? 0.0); // opt reassign: 42.5 + + // Assignment of computed value to optional + iw2: ?f32 = null; + a: ?f32 = 10.0; + if v := a { iw2 = v + 5.0; } + print("opt compute assign: {}\n", iw2 ?? 0.0); // opt compute assign: 15.0 + + // Re-assign optional back to null + iw2 = null; + print("opt re-null: {}\n", iw2 ?? 99.0); // opt re-null: 99.0 + } + // Generic function with ?T return { first_pos :: ($T: Type, a: T, b: T) -> ?T { diff --git a/src/codegen.zig b/src/codegen.zig deleted file mode 100644 index f0d5c15..0000000 --- a/src/codegen.zig +++ /dev/null @@ -1,11905 +0,0 @@ -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"); -const ir = @import("ir/ir.zig"); - -/// Feature flag: use the IR interpreter for comptime evaluation instead of the bytecode VM. -const USE_IR_COMPTIME = true; - -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; -} - -/// Detect `$T: Type` parameter declarations (not `s: $T` references). -/// For `$T: Type`, the parser sets param.name = "T" and type_expr = {name="T", is_generic=true}. -/// For `s: $T`, param.name = "s" and type_expr = {name="T", is_generic=true}. -fn isTypeParamDecl(param: ast.Param) bool { - return param.type_expr.data == .type_expr and - param.type_expr.data.type_expr.is_generic and - std.mem.eql(u8, param.name, param.type_expr.data.type_expr.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), - // Flow-sensitive narrowing: tracks variables narrowed from ?T to T - narrowed_types: std.StringHashMap(NarrowedInfo), - // 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, - // Expected closure type for inferring untyped closure params - closure_expected_type: ?types.Type.ClosureTypeInfo = null, - // 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), - // Local compile-time constant values (for :: decls with known values) - local_comptime_constants: std.StringHashMap(comptime_mod.Value), - // 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 }, - // Current source file path (for error reporting in imported files) - current_source_file: ?[]const u8 = null, - // Import source map (path → source text, for error reporting) - import_sources: ?*const std.StringHashMap([:0]const u8) = null, - // 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 IR module for comptime evaluation (built once, reused) - cached_ir_module: ?ir.Module = null, - // 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), - // AST nodes whose bodies were generated in Pass 4 (to avoid double generation in main) - generated_bodies: std.AutoHashMap(*const Node, void), - // 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), - // Named library constants: sx name → lib filename (e.g. "libc" → "c") - library_constants: std.StringHashMap([]const u8), - // Foreign function rename map: sx name → C symbol name (e.g. "write_fd" → "write") - foreign_name_map: std.StringHashMap([]const u8), - // 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), - // Tuple alloca → type mapping (since tuples are anonymous, we track their types by alloca pointer) - tuple_alloca_types: std.AutoHashMap(usize, Type), - // UFCS alias map: alias name → target function name - ufcs_aliases: std.StringHashMap([]const u8), - // Closure thunk cache: original function name → thunk LLVM function (dedup) - closure_thunks: std.StringHashMap(c.LLVMValueRef), - // Protocol declarations: maps protocol name → ProtocolDecl AST node - protocol_decls: std.StringHashMap(ast.ProtocolDecl), - // Impl registrations: maps "ProtoName\x00TypeName" → ImplBlock AST node - impl_blocks: std.StringHashMap(ast.ImplBlock), - // Protocol thunks: maps "ProtoName\x00TypeName" → array of thunk LLVM functions (one per protocol method, in order) - protocol_thunks: std.StringHashMap([]const c.LLVMValueRef), - // 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, - source_file: ?[]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, - }; - - /// Info for a flow-narrowed optional variable: ?T → T in a checked scope - const NarrowedInfo = struct { - narrowed_ty: Type, // the inner type T (not ?T) - payload_ptr: c.LLVMValueRef, // alloca holding the unwrapped value - }; - - /// 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), - .narrowed_types = std.StringHashMap(NarrowedInfo).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), - .local_comptime_constants = std.StringHashMap(comptime_mod.Value).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, - .generated_bodies = std.AutoHashMap(*const Node, void).init(allocator), - .foreign_libraries = std.ArrayList([]const u8).empty, - .foreign_fns = std.StringHashMap(void).init(allocator), - .library_constants = std.StringHashMap([]const u8).init(allocator), - .foreign_name_map = std.StringHashMap([]const u8).init(allocator), - .global_mutable_vars = std.StringHashMap(NamedValue).init(allocator), - .function_return_types = std.StringHashMap(Type).init(allocator), - .tuple_alloca_types = std.AutoHashMap(usize, Type).init(allocator), - .ufcs_aliases = std.StringHashMap([]const u8).init(allocator), - .closure_thunks = std.StringHashMap(c.LLVMValueRef).init(allocator), - .protocol_decls = std.StringHashMap(ast.ProtocolDecl).init(allocator), - .impl_blocks = std.StringHashMap(ast.ImplBlock).init(allocator), - .protocol_thunks = std.StringHashMap([]const c.LLVMValueRef).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(); - self.library_constants.deinit(); - self.foreign_name_map.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 self.resolveTypeFromName(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.current_source_file = self.current_source_file; - 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.current_source_file = self.current_source_file; - 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; - } - - /// Lookup a named value in locals or global mutables (for field access/assignment). - fn getNamedOrGlobal(self: *CodeGen, name: []const u8) ?NamedValue { - if (self.named_values.get(name)) |nv| return nv; - return self.global_mutable_vars.get(name); - } - - /// 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 = self.resolveTypeFromName(info.element_name) orelse unreachable; - return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length); - }, - .vector_type => |info| { - const elem_ty = self.resolveTypeFromName(info.element_name) orelse unreachable; - return c.LLVMVectorType(self.typeToLLVM(elem_ty), info.length); - }, - .pointer_type, .many_pointer_type, .function_type => self.ptrType(), - .closure_type => self.getClosureStructType(), - .optional_type => |info| { - // ?Closure(...) → same layout as Closure { ptr, ptr } — fn_ptr null = none - if (std.mem.startsWith(u8, info.child_name, "Closure(")) { - return self.getClosureStructType(); - } - // ?*T, ?[*]T, ?fn → bare pointer (null = none) - const child_type = self.resolveTypeFromName(info.child_name) orelse unreachable; - if (child_type.isPointer() or child_type.isManyPointer() or child_type.isFunctionType()) { - return self.ptrType(); - } - if (child_type.isClosureType()) { - return self.getClosureStructType(); - } - // ?T → { T, i1 } struct - var field_types: [2]c.LLVMTypeRef = .{ - self.typeToLLVM(child_type), - self.i1Type(), - }; - return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); - }, - .any_type => self.getAnyStructType(), - .meta_type => self.ptrType(), - .tuple_type => |info| { - const n: c_uint = @intCast(info.field_types.len); - const field_llvm_types = self.allocator.alloc(c.LLVMTypeRef, info.field_types.len) catch unreachable; - for (info.field_types, 0..) |ft, i| { - field_llvm_types[i] = self.typeToLLVM(ft); - } - return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0); - }, - }; - } - - 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); - - // Optional: branch on has_value — some prints inner value, none prints "null" - if (in_ty.isOptional()) { - const has_val = self.optionalHasValue(val, in_ty); - const some_bb = self.appendBB("opt_any_some"); - const none_bb = self.appendBB("opt_any_none"); - const merge_bb = self.appendBB("opt_any_merge"); - _ = c.LLVMBuildCondBr(self.builder, has_val, some_bb, none_bb); - - // Some: extract payload and wrap as Any - self.positionAt(some_bb); - const payload = self.optionalPayload(val, in_ty); - const child_name = in_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse .void_type; - const some_any = try self.buildAnyValue(payload, child_ty); - const some_out = self.getCurrentBlock(); - self.br(merge_bb); - - // None: wrap "null" string as Any - self.positionAt(none_bb); - const null_str = c.LLVMBuildGlobalStringPtr(self.builder, "null", "null_str"); - const null_len = self.constInt64(4); - const null_slice = self.buildStringSlice(null_str, null_len); - const none_any = try self.buildAnyValue(null_slice, .string_type); - const none_out = self.getCurrentBlock(); - self.br(merge_bb); - - self.positionAt(merge_bb); - var phi_vals = std.ArrayList(c.LLVMValueRef).empty; - var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; - try phi_vals.append(self.allocator, some_any); - try phi_bbs.append(self.allocator, some_out); - try phi_vals.append(self.allocator, none_any); - try phi_bbs.append(self.allocator, none_out); - return try self.buildPhiNode(&phi_vals, &phi_bbs, any_ty, "opt_any_phi"); - } - - // []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, .function_type => self.ptrToInt(val, "any_ptr"), - .closure_type => self.allocaStoreAsI64(self.getClosureStructType(), val, "any_closure"), - .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.?; - } - - fn getClosureStructType(self: *CodeGen) c.LLVMTypeRef { - // Closure = { fn_ptr: ptr, env: ptr } - var field_types = [_]c.LLVMTypeRef{ - self.ptrType(), // fn_ptr - self.ptrType(), // env - }; - return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); - } - - /// 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); - } - - /// Wrap a value into an optional { value, i1 1 }. - /// For pointer optionals, the value is already a pointer — return as-is. - fn wrapOptional(self: *CodeGen, value: c.LLVMValueRef, opt_ty: Type) c.LLVMValueRef { - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse unreachable; - if (child_ty.isPointer() or child_ty.isManyPointer() or child_ty.isFunctionType()) { - return value; // pointer is already nullable — just pass through - } - if (child_ty.isClosureType()) { - return value; // closure {fn_ptr, env} — fn_ptr null means none - } - const llvm_opt_ty = self.typeToLLVM(opt_ty); - var result = self.getUndef(llvm_opt_ty); - result = c.LLVMBuildInsertValue(self.builder, result, value, 0, "opt_val"); - result = c.LLVMBuildInsertValue(self.builder, result, c.LLVMConstInt(self.i1Type(), 1, 0), 1, "opt_some"); - return result; - } - - /// Create a null optional value (none). - /// For pointer optionals: null pointer. For value optionals: { undef, i1 0 }. - fn makeNullOptional(self: *CodeGen, opt_ty: Type) c.LLVMValueRef { - const llvm_opt_ty = self.typeToLLVM(opt_ty); - return c.LLVMConstNull(llvm_opt_ty); - } - - /// Check if an optional has a value. Returns an i1. - /// For pointer optionals: ptr != null. For value optionals: extractvalue i1 flag. - fn optionalHasValue(self: *CodeGen, opt_val: c.LLVMValueRef, opt_ty: Type) c.LLVMValueRef { - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse unreachable; - if (child_ty.isPointer() or child_ty.isManyPointer() or child_ty.isFunctionType()) { - return c.LLVMBuildICmp(self.builder, c.LLVMIntNE, opt_val, c.LLVMConstNull(self.ptrType()), "opt_nonnull"); - } - if (child_ty.isClosureType()) { - // Check fn_ptr (index 0) != null - const fn_ptr = self.extractValue(opt_val, 0, "cl_fn_chk"); - return c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(self.ptrType()), "opt_cl_nonnull"); - } - return c.LLVMBuildExtractValue(self.builder, opt_val, 1, "opt_flag"); - } - - /// Extract the payload from an optional (no check — caller must ensure has_value). - /// For pointer optionals: returns the pointer. For value optionals: extractvalue payload. - fn optionalPayload(self: *CodeGen, opt_val: c.LLVMValueRef, opt_ty: Type) c.LLVMValueRef { - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse unreachable; - if (child_ty.isPointer() or child_ty.isManyPointer() or child_ty.isFunctionType()) { - return opt_val; // for pointer optionals, the value IS the pointer - } - if (child_ty.isClosureType()) { - return opt_val; // closure struct is the payload — fn_ptr null = none - } - return c.LLVMBuildExtractValue(self.builder, opt_val, 0, "opt_payload"); - } - - 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 extractClosureField(self: *CodeGen, val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef { - if (std.mem.eql(u8, field, "fn_ptr")) { - return self.extractValue(val, 0, "fn_ptr"); - } - if (std.mem.eql(u8, field, "env")) { - return self.extractValue(val, 1, "env"); - } - return self.emitErrorFmt("no field '{s}' on Closure (available: .fn_ptr, .env)", .{field}); - } - - 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"); - self.root_decls = root.data.root.decls; - self.builtins = Builtins.init(self.module, self.context); - const decls = root.data.root.decls; - - try self.collectLibraries(decls); - - // Each phase depends on the previous: types must exist before fields can - // reference them, fields must be resolved before function signatures can - // use them as parameter/return types, and all signatures must be registered - // before emitting function bodies. - try self.registerTypes(decls); - try self.resolveFields(decls); - try self.registerFunctions(decls); - try self.generateBodies(decls); - try self.generateDeferred(); - } - - // ── Phase 0: collect library constants ────────────────────────────── - - fn collectLibraries(self: *CodeGen, decls: []const *Node) !void { - for (decls) |decl| { - self.current_source_file = decl.source_file; - switch (decl.data) { - .library_decl => |ld| { - try self.library_constants.put(ld.name, ld.lib_name); - }, - .namespace_decl => |ns| { - for (ns.decls) |nd| { - if (nd.data == .library_decl) { - const nld = nd.data.library_decl; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, nld.name }); - try self.library_constants.put(qualified, nld.lib_name); - } - } - }, - else => {}, - } - } - } - - // ── Phase 1: register type names (no field resolution yet) ────────── - - fn registerTypes(self: *CodeGen, decls: []const *Node) !void { - for (decls) |decl| { - self.current_source_file = decl.source_file; - switch (decl.data) { - .fn_decl => |fd| { - if (fd.body.data == .builtin_expr) { - try self.builtin_functions.put(fd.name, {}); - } else if (fd.type_params.len > 0) { - try self.generic_templates.put(fd.name, fd); - } - 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) { - try self.registerTaggedEnumName(ed); - } else { - 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, {}); - // enum variant values (no resolveType needed) - 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) { - const val_node = ed.variant_values[i].?; - values[i] = switch (val_node.data) { - .int_literal => |il| il.value, - else => @as(i64, @intCast(i)), - }; - } else if (ed.is_flags) { - values[i] = @as(i64, 1) << @intCast(i); - } else { - values[i] = @intCast(i); - } - } - try self.enum_variant_values.put(ed.name, values); - } - }, - .struct_decl => |sd| try self.registerStructName(sd), - .union_decl => |ud| try self.registerUnionName(ud), - .const_decl => |cd| { - if (cd.value.data == .builtin_expr) { - // skip - } 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) { - 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| { - 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| { - 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)) { - 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 }); - } - } - } - } - }, - .namespace_decl => |ns| try self.registerNamespaceTypes(ns), - .protocol_decl => |pd| try self.registerProtocolDecl(pd), - else => {}, - } - } - } - - // ── Phase 2: resolve struct/union/enum field types ────────────────── - - fn resolveFields(self: *CodeGen, decls: []const *Node) !void { - for (decls) |decl| { - self.current_source_file = decl.source_file; - switch (decl.data) { - .enum_decl => |ed| { - if (ed.variant_types.len > 0) { - try self.resolveTaggedEnumFields(ed); - } else if (ed.backing_type) |bt_node| { - const bt = self.resolveType(bt_node); - try self.enum_backing_types.put(ed.name, self.typeToLLVM(bt)); - } - }, - .struct_decl => |sd| try self.resolveStructFields(sd), - .union_decl => |ud| try self.resolveUnionFields(ud), - .namespace_decl => |ns| try self.resolveNamespaceFields(ns), - else => {}, - } - } - } - - // ── Phase 3: register function signatures ─────────────────────────── - - fn registerFunctions(self: *CodeGen, decls: []const *Node) !void { - for (decls) |decl| { - self.current_source_file = decl.source_file; - switch (decl.data) { - .fn_decl => |fd| { - if (fd.body.data != .builtin_expr and fd.type_params.len == 0) { - _ = try self.registerFnDecl(fd, fd.name); - } - }, - .struct_decl => |sd| { - try self.registerStructMethods(sd); - try self.registerStructConstants(sd); - }, - .const_decl => |cd| { - if (cd.value.data == .builtin_expr or cd.value.data == .type_expr) { - // already handled - } else if (cd.value.data == .lambda) { - try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); - } else if (cd.value.data == .call) { - 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.contains(cn)) { - // type instantiation, already handled - } else if (self.generic_templates.contains(cn)) { - if (!self.type_registry.contains(cd.name)) { - try self.registerTopLevelConstant(cd); - } - } else if (self.builtin_functions.contains(cn)) { - if (!self.type_registry.contains(cd.name)) { - try self.registerTopLevelConstant(cd); - } - } else { - try self.registerTopLevelConstant(cd); - } - } else { - try self.registerTopLevelConstant(cd); - } - } else 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); - } else { - try self.registerTopLevelConstant(cd); - } - }, - .comptime_expr => |ct| { - try self.comptime_side_effects.append(self.allocator, ct.expr); - }, - .namespace_decl => |ns| try self.registerNamespaceFunctions(ns), - .var_decl => |vd| try self.registerGlobalVar(vd), - .ufcs_alias => |ua| try self.ufcs_aliases.put(ua.name, ua.target), - .impl_block => |ib| try self.registerImplBlock(ib), - else => {}, - } - } - } - - // ── Phase 4: generate function bodies ─────────────────────────────── - - fn generateBodies(self: *CodeGen, decls: []const *Node) !void { - for (decls) |decl| { - self.current_source_file = decl.source_file; - switch (decl.data) { - .fn_decl => |fd| { - if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) { - // 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, .source_file = self.current_source_file }); - } else { - try self.genFnBody(fd, fd.name); - } - try self.generated_bodies.put(decl, {}); - } - }, - .const_decl => |cd| { - if (cd.value.data == .lambda) { - try self.genLambdaBody(cd.name, cd.value.data.lambda); - try self.generated_bodies.put(decl, {}); - } - }, - .namespace_decl => |ns| { - try self.genNamespaceBodies(ns); - for (ns.decls) |nd| { - if (nd.data == .struct_decl) { - try self.genStructMethodBodies(nd.data.struct_decl); - } - } - }, - .struct_decl => |sd| try self.genStructMethodBodies(sd), - .impl_block => |ib| try self.genImplMethodBodies(ib), - else => {}, - } - } - } - - // ── Phase 5: deferred bodies + comptime side effects ──────────────── - - fn generateDeferred(self: *CodeGen) !void { - for (self.deferred_fn_bodies.items) |deferred| { - const saved_ns = self.current_namespace; - const saved_sf = self.current_source_file; - self.current_namespace = deferred.namespace; - self.current_source_file = deferred.source_file; - defer self.current_namespace = saved_ns; - defer self.current_source_file = saved_sf; - try self.genFnBody(deferred.fd, deferred.name); - } - - for (self.comptime_side_effects.items) |expr| { - _ = try self.comptimeEval(expr, .void_type); - } - } - - /// Evaluate a comptime expression using the bytecode VM or the IR interpreter. - /// When USE_IR_COMPTIME is true, tries the IR interpreter first and falls back - /// to the bytecode VM if the interpreter can't handle the expression. - fn comptimeEval(self: *CodeGen, expr: *Node, expected_type: Type) !comptime_mod.Value { - if (USE_IR_COMPTIME) { - if (self.tryIrComptimeEval(expr)) |result| { - return result; - } - // IR interpreter can't handle this expression — fall back to VM - } - return self.vmComptimeEval(expr, expected_type); - } - - /// Evaluate a comptime expression using the bytecode VM (original path). - fn vmComptimeEval(self: *CodeGen, expr: *Node, expected_type: Type) !comptime_mod.Value { - _ = expected_type; - - 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); - vm.setupComptimeContext() catch {}; - return vm.execute(&chunk) catch |err| { - return self.emitErrorFmt("comptime execution failed: {s}", .{@errorName(err)}); - }; - } - - /// Try to evaluate a comptime expression using the IR interpreter. - /// Returns null if the interpreter can't handle the expression (no diagnostics emitted). - fn tryIrComptimeEval(self: *CodeGen, expr: *Node) ?comptime_mod.Value { - // Build the IR module once (lowering all root decls), then reuse it - if (self.cached_ir_module == null) { - self.cached_ir_module = ir.Module.init(self.allocator); - var lowering = ir.Lowering.init(&self.cached_ir_module.?); - lowering.lowerDecls(self.root_decls); - } - - // Create a comptime function that wraps the expression - var lowering = ir.Lowering.init(&self.cached_ir_module.?); - const func_id = lowering.createComptimeFunction("ct_eval", expr, .s64); - - // Interpret the comptime function - var interp = ir.Interpreter.init(&self.cached_ir_module.?, self.allocator); - const result = interp.call(func_id, &.{}) catch return null; - - // Convert ir.Value → comptime_mod.Value; return null if the - // interpreter produced void (couldn't fully evaluate the expression). - const cv = irValueToComptimeValue(result); - return if (cv == .void_val) null else cv; - } - - /// Convert an IR interpreter value to a comptime module value. - fn irValueToComptimeValue(val: ir.Value) comptime_mod.Value { - return switch (val) { - .int => |v| .{ .int_val = v }, - .float => |v| .{ .float_val = v }, - .boolean => |v| .{ .bool_val = v }, - .string => |v| .{ .string_val = v }, - .void_val => .{ .void_val = {} }, - .null_val => .{ .null_val = {} }, - .aggregate => .{ .void_val = {} }, // TODO: struct/array conversion - .undef => .{ .void_val = {} }, - .slot_ptr, .func_ref, .closure, .type_tag, .heap_ptr => .{ .void_val = {} }, - }; - } - - /// Try to evaluate a :: call expression entirely at compile time. - /// Works for any function where all args are comptime-known. - /// Returns the result string if successful, null to fall through to runtime codegen. - fn tryComptimeCallEval(self: *CodeGen, cd: ast.ConstDecl) ?comptime_mod.Value { - const call_node = cd.value.data.call; - - // Resolve callee name - const callee_name = if (call_node.callee.data == .identifier) - call_node.callee.data.identifier.name - else if (call_node.callee.data == .field_access) blk: { - const fa = call_node.callee.data.field_access; - if (fa.object.data == .identifier) { - const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch return null; - break :blk qualified; - } - break :blk @as(?[]const u8, null); - } else null; - - const cn = callee_name orelse return null; - - // Look up the function — either generic template or regular fn_decl - const fd = self.findFnDecl(cn) orelse return null; - - // Resolve all args to comptime values - var arg_values = self.allocator.alloc(comptime_mod.Value, call_node.args.len) catch return null; - for (call_node.args, 0..) |arg, i| { - arg_values[i] = self.resolveComptimeArg(arg) orelse return null; - } - - // Set up VM and push all args onto the stack - var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self); - vm.setupComptimeContext() catch {}; - for (arg_values) |val| { - vm.push(val) catch return null; - } - - // Compile and invoke the function — the VM handles #insert, variadics, etc. - vm.compileFunctionAndInvoke(cn, fd, @intCast(arg_values.len)) catch return null; - - // Run the VM to completion - const result = vm.run() catch return null; - return result; - } - - /// Find a function declaration by name in generic_templates or root_decls. - fn findFnDecl(self: *CodeGen, name: []const u8) ?ast.FnDecl { - // Check generic templates first - if (self.generic_templates.get(name)) |fd| return fd; - // Search root_decls - for (self.root_decls) |decl| { - switch (decl.data) { - .fn_decl => |fd| { - if (std.mem.eql(u8, fd.name, name)) return fd; - }, - .namespace_decl => |ns| { - for (ns.decls) |d| { - if (d.data == .fn_decl and std.mem.eql(u8, d.data.fn_decl.name, name)) - return d.data.fn_decl; - } - }, - else => {}, - } - } - return null; - } - - /// Resolve an AST node to a comptime Value using local_comptime_constants. - /// Handles literals, identifiers, and field accesses like `body.len`. - fn resolveComptimeArg(self: *CodeGen, node: *Node) ?comptime_mod.Value { - return switch (node.data) { - .string_literal => |sl| .{ .string_val = if (sl.is_raw) sl.raw else unescape.unescapeString(self.allocator, sl.raw) catch return null }, - .int_literal => |il| .{ .int_val = il.value }, - .identifier => |id| self.local_comptime_constants.get(id.name), - .field_access => |fa| { - const base = self.resolveComptimeArg(fa.object) orelse return null; - if (std.mem.eql(u8, fa.field, "len")) { - if (base == .string_val) { - return .{ .int_val = @intCast(base.string_val.len) }; - } - if (base == .array_val) { - return .{ .int_val = @intCast(base.array_val.elements.len) }; - } - } - return null; - }, - else => null, - }; - } - - /// 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, .byte_ptr_val, .union_val, .any_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 } }; - } - // Optional type: ?T - if (tn.data == .optional_type_expr) { - const ote = tn.data.optional_type_expr; - const inner_type = self.resolveType(ote.inner_type); - const inner_name = inner_type.displayName(self.allocator) catch unreachable; - return .{ .optional_type = .{ .child_name = inner_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, - } }; - } - // Closure type: Closure(ParamTypes) -> ReturnType - if (tn.data == .closure_type_expr) { - const cte = tn.data.closure_type_expr; - var param_types = std.ArrayList(Type).empty; - for (cte.param_types) |pt| { - param_types.append(self.allocator, self.resolveType(pt)) catch return .void_type; - } - const ret_ty = if (cte.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 .{ .closure_type = .{ - .param_types = param_types.toOwnedSlice(self.allocator) catch return .void_type, - .return_type = ret_ptr, - } }; - } - // Tuple type: (T1, T2) or (T1,) - if (tn.data == .tuple_type_expr) { - const tte = tn.data.tuple_type_expr; - const field_types = self.allocator.alloc(Type, tte.field_types.len) catch return .void_type; - for (tte.field_types, 0..) |ft, i| { - field_types[i] = self.resolveType(ft); - } - return .{ .tuple_type = .{ - .field_types = field_types, - .field_names = tte.field_names, - } }; - } - // 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; - // Self type: resolves to concrete type when inside impl, or *void when in protocol decl context - if (std.mem.eql(u8, name, "Self")) { - if (self.current_namespace) |ns| { - return .{ .struct_type = ns }; - } - // In protocol context (no namespace): Self erased to *void for dynamic dispatch - return .{ .pointer_type = .{ .pointee_name = "void" } }; - } - // 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 => {}, - } - } - }, - } - } - } - // Inline type declarations: resolve by registered name - 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 == .union_decl) { - const un = tn.data.union_decl.name; - if (self.type_registry.get(un)) |e| switch (e) { - .union_info => return .{ .union_type = un }, - .tagged_enum => return .{ .union_type = un }, - else => {}, - }; - } - 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); - } - } - - // Check protocol constraints on struct type params: $T: Type/Eq/Hashable - for (sd.type_params) |tp| { - if (tp.protocol_constraints.len > 0) { - if (type_bindings.get(tp.name)) |bound_ty| { - const type_name = bound_ty.toName() orelse "unknown"; - for (tp.protocol_constraints) |proto_name| { - const key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ proto_name, type_name }); - if (!self.impl_blocks.contains(key)) { - return self.emitErrorFmt("{s} does not implement {s}", .{ type_name, proto_name }); - } - } - } - } - } - - 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 findStructInBody(_: *CodeGen, body: *Node) ?ast.StructDecl { - if (body.data == .struct_decl) return body.data.struct_decl; - 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 == .struct_decl) return val.data.struct_decl; - } - } - if (stmt.data == .struct_decl) return stmt.data.struct_decl; - } - } - return null; - } - - 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; - // Skip $T: Type params — erased at instantiation time (param name == type name) - if (isTypeParamDecl(param)) 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) !c.LLVMValueRef { - const is_foreign = fd.body.data == .foreign_expr; - // For foreign functions: resolve C symbol name (rename) and validate library ref - const actual_llvm_name = if (is_foreign) blk: { - const fe = fd.body.data.foreign_expr; - // Validate library reference - if (fe.library_ref) |lib_ref| { - if (!self.library_constants.contains(lib_ref)) { - return self.emitErrorFmt("unknown library '{s}' in #foreign", .{lib_ref}); - } - } - // Use C symbol name if provided, otherwise use the sx name - const c_name = fe.c_name orelse llvm_name; - // Track rename mapping for call resolution - if (fe.c_name != null and !std.mem.eql(u8, c_name, llvm_name)) { - try self.foreign_name_map.put(llvm_name, c_name); - } - break :blk c_name; - } else llvm_name; - const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign); - const name_z = try self.allocator.dupeZ(u8, actual_llvm_name); - const function = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); - // Track foreign functions for ABI lowering at call sites (use sx name for call-site lookup) - if (is_foreign) { - try self.foreign_fns.put(llvm_name, {}); - // Also track under the C name for direct lookups - if (!std.mem.eql(u8, actual_llvm_name, llvm_name)) { - try self.foreign_fns.put(actual_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; - } - } - return function; - } - - /// registerTypes helper: register type names within a namespace. - fn registerNamespaceTypes(self: *CodeGen, ns: ast.NamespaceDecl) !void { - try self.namespaces.put(ns.name, {}); - for (ns.decls) |decl| { - switch (decl.data) { - .enum_decl => |ed| { - if (ed.variant_types.len > 0) { - try self.registerTaggedEnumName(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 }); - } - }, - .struct_decl => |sd| { - try self.registerStructName(sd); - 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.registerUnionName(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 == .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); - }, - .protocol_decl => |pd| { - try self.registerProtocolDecl(pd); - }, - else => {}, - } - } - } - - /// resolveFields helper: resolve field types within a namespace. - fn resolveNamespaceFields(self: *CodeGen, ns: ast.NamespaceDecl) !void { - for (ns.decls) |decl| { - switch (decl.data) { - .enum_decl => |ed| { - if (ed.variant_types.len > 0) { - try self.resolveTaggedEnumFields(ed); - } else { - if (ed.backing_type) |bt_node| { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); - const bt = self.resolveType(bt_node); - try self.enum_backing_types.put(qualified, self.typeToLLVM(bt)); - } - } - }, - .struct_decl => |sd| try self.resolveStructFields(sd), - .union_decl => |ud| try self.resolveUnionFields(ud), - else => {}, - } - } - } - - /// registerFunctions helper: register function signatures within a namespace. - fn registerNamespaceFunctions(self: *CodeGen, ns: ast.NamespaceDecl) !void { - 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) { - _ = try self.registerFnDecl(fd, fd.name); - try self.foreign_fns.put(qualified, {}); - const fe = fd.body.data.foreign_expr; - if (fe.c_name) |c_name| { - if (!std.mem.eql(u8, c_name, fd.name)) { - try self.foreign_name_map.put(qualified, c_name); - } - } - 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); - } - }, - .struct_decl => |sd| { - try self.registerStructMethods(sd); - try self.registerStructConstants(sd); - }, - .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); - } - // type aliases already handled in registerNamespaceTypes - }, - .var_decl => |vd| { - try self.registerGlobalVar(vd); - }, - .ufcs_alias => |ua| { - try self.ufcs_aliases.put(ua.name, ua.target); - }, - .protocol_decl => { - // already registered in registerNamespaceTypes - }, - .impl_block => |ib| { - try self.registerImplBlock(ib); - }, - 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, .source_file = self.current_source_file }); - } 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); - } - }, - .impl_block => |ib| { - try self.genImplMethodBodies(ib); - }, - else => {}, - } - } - } - - /// Generate LLVM bodies for non-generic methods declared inside a struct. - fn genStructMethodBodies(self: *CodeGen, sd: ast.StructDecl) !void { - if (sd.methods.len == 0) return; - // Generic struct methods are instantiated on demand — skip body generation here - if (sd.type_params.len > 0) return; - - const saved_ns = self.current_namespace; - self.current_namespace = sd.name; - defer self.current_namespace = saved_ns; - - for (sd.methods) |method_node| { - const fd = method_node.data.fn_decl; - if (fd.type_params.len > 0) continue; // generic methods instantiated on demand - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, fd.name }); - if (shouldDeferFnBody(fd)) { - try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = sd.name, .source_file = self.current_source_file }); - } else { - try self.genFnBody(fd, qualified); - } - } - } - - 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; - var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)); - // Foreign rename fallback - if (callee_fn == null) { - if (self.foreign_name_map.get(callee_name)) |c_name| { - var rbuf: [256]u8 = undefined; - callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf)); - } - } - const resolved_fn = callee_fn orelse return Type.s(64); - const fn_type = c.LLVMGlobalGetValueType(resolved_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| { - if (target_ty.isFloat()) { - return c.LLVMConstReal(llvm_ty, @floatFromInt(@as(i64, lit.value))); - } - 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); - }, - .string_literal => |sl| { - const content = if (sl.is_raw) sl.raw else (unescape.unescapeString(self.allocator, sl.raw) catch return null); - return self.buildConstStrGlobal(content); - }, - .struct_literal => |sl| { - return self.evalConstantStruct(sl, target_ty); - }, - .enum_literal => |el| { - if (target_ty.isEnum()) { - const variants = self.lookupEnumVariants(target_ty.enum_type) orelse return null; - for (variants, 0..) |vname, i| { - if (std.mem.eql(u8, vname, el.name)) { - return c.LLVMConstInt(llvm_ty, @intCast(i), 0); - } - } - } - return null; - }, - else => return null, - } - } - - /// Evaluate a struct literal as a constant (for global initializers). - fn evalConstantStruct(self: *CodeGen, sl: ast.StructLiteral, target_ty: Type) ?c.LLVMValueRef { - const struct_name = sl.struct_name orelse target_ty.struct_type; - const info = self.lookupStructInfo(struct_name) orelse return null; - - if (info.field_names.len > 32) return null; - var field_vals: [32]c.LLVMValueRef = undefined; - const n = info.field_names.len; - - // Initialize with undef for each field - for (0..n) |i| { - field_vals[i] = c.LLVMGetUndef(self.typeToLLVM(info.field_types[i])); - } - - // Fill in provided field values - for (sl.field_inits) |fi| { - const field_name = fi.name orelse continue; // skip positional for now - const idx = blk: { - for (info.field_names, 0..) |fn_name, j| { - if (std.mem.eql(u8, fn_name, field_name)) break :blk j; - } - return null; // unknown field - }; - const val = self.evalConstant(fi.value, info.field_types[idx]) orelse return null; - field_vals[idx] = val; - } - - return c.LLVMConstNamedStruct(info.llvm_type, @ptrCast(&field_vals), @intCast(n)); - } - - /// 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 sx_ty = if (cd.type_annotation) |ta| - self.resolveType(ta) - else - self.inferType(cd.value); - 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); - - // Try constant-evaluable initializer → register as comptime_global for JIT compatibility - if (vd.value) |val_node| { - if (self.evalConstant(val_node, sx_ty)) |const_val| { - c.LLVMSetInitializer(global, const_val); - c.LLVMSetGlobalConstant(global, 1); - try self.comptime_globals.put(vd.name, .{ - .global = global, - .ty = sx_ty, - .expr = val_node, - .is_resolved = true, - }); - return; - } - } - - // Non-constant globals: mutable, initialized with undef (set at runtime, e.g. by load_gl) - c.LLVMSetInitializer(global, self.getUndef(llvm_ty)); - 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); - // If raw_val is already a struct value (not a pointer), return it directly - if (c.LLVMGetTypeKind(c.LLVMTypeOf(raw_val)) != c.LLVMPointerTypeKind) - return raw_val; - return c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval"); - } else if (ret_type.isTuple()) { - const llvm_ty = self.typeToLLVM(ret_type); - if (c.LLVMGetTypeKind(c.LLVMTypeOf(raw_val)) != c.LLVMPointerTypeKind) - return raw_val; - return c.LLVMBuildLoad2(self.builder, llvm_ty, raw_val, "retval"); - } else if (ret_type.isUnion()) { - const uname = ret_type.union_type; - const resolved = self.resolveAlias(uname); - // Try C-style (untagged) union first - if (self.lookupUnionInfo(resolved)) |info| { - if (c.LLVMGetTypeKind(c.LLVMTypeOf(raw_val)) != c.LLVMPointerTypeKind) - return raw_val; - return c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval"); - } - const info = try self.getTaggedEnumInfo(resolved); - return self.loadIfPointer(raw_val, info.llvm_type, "retval"); - } else { - // If ret_type is a pointer/many-pointer/fn-pointer and the LLVM value is already - // an opaque ptr, return it directly. llvmTypeToSxType would misclassify LLVM ptr - // as string_type, causing a bogus slice_to_ptr conversion and crash. - if ((ret_type.isPointer() or ret_type.isManyPointer() or ret_type == .function_type) and - c.LLVMGetTypeKind(c.LLVMTypeOf(raw_val)) == c.LLVMPointerTypeKind) - { - return raw_val; - } - 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(); - self.narrowed_types.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)); - } - - // Auto-initialize context with default GPA (if std.sx imported) - if (is_main) { - try self.emitDefaultContextInit(); - } - - // 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; - const stmts = body.data.block.stmts; - for (stmts, 0..) |stmt, stmt_idx| { - // Last statement with xx prefix: use return type as target context for implicit return - if (stmt_idx == stmts.len - 1 and ret_sx_type != .void_type and - stmt.data == .unary_op and stmt.data.unary_op.op == .xx) - { - last_val = try self.genExprAsType(stmt, ret_sx_type); - } else { - 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 { - // Block-body without explicit return type → void (same as named functions) - // Expression-body without explicit return type → infer from expression - const ret_sx_type = if (lambda.return_type != null) self.resolveType(lambda.return_type) else if (lambda.body.data == .block) Type.void_type else 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); - try self.function_return_types.put(name, ret_sx_type); - } - - fn genLambdaBody(self: *CodeGen, name: []const u8, lambda: ast.Lambda) !void { - self.named_values.clearRetainingCapacity(); - self.narrowed_types.clearRetainingCapacity(); - - // Block-body without explicit return type → void (same as named functions) - // Expression-body without explicit return type → infer from expression - const ret_sx_type = if (lambda.return_type != null) self.resolveType(lambda.return_type) else if (lambda.body.data == .block) Type.void_type else 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)); - } - - // Block-body lambda: generate statements like genFnBody - if (lambda.body.data == .block) { - try self.pushScope(); - var last_val: c.LLVMValueRef = null; - for (lambda.body.data.block.stmts) |stmt| { - last_val = try self.genStmt(stmt); - } - // Only add terminator if block doesn't already have one (from explicit return) - const current_bb = self.getCurrentBlock(); - if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { - try self.popScope(); - 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) { - self.retVoid(); - } else if (effective_last_val) |val| { - const prepared = try self.prepareReturnValue(val, ret_sx_type); - self.ret(prepared); - } else { - _ = c.LLVMBuildUnreachable(self.builder); - } - } - } else { - // Expression-body lambda: (params) => expr - const ret_val = try self.genExpr(lambda.body); - if (ret_val) |val| { - const prepared = try self.prepareReturnValue(val, ret_sx_type); - self.ret(prepared); - } else if (ret_sx_type == .void_type) { - self.retVoid(); - } else { - _ = c.LLVMBuildUnreachable(self.builder); - } - } - } - - 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; - const saved_narrowed = self.narrowed_types; - self.named_values = std.StringHashMap(NamedValue).init(self.allocator); - self.narrowed_types = std.StringHashMap(NarrowedInfo).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; - - // Build or register the LLVM function, keeping a direct reference - // (LLVMGetNamedFunction returns the first fn with that name, which - // may differ when multiple local functions share a name) - const function = blk: { - 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); - const func = c.LLVMAddFunction(self.module, name_z2.ptr, fn_type); - try self.function_return_types.put(fd.name, ret_sx_type); - break :blk func; - } else { - break :blk try self.registerFnDecl(fd, fd.name); - } - }; - - // Skip if this exact AST node was already generated in Pass 4 - // (top-level fn_decls appear both in Pass 4 and main's body) - if (self.generated_bodies.contains(node)) { - self.named_values.deinit(); - self.named_values = saved_named; - self.narrowed_types = saved_narrowed; - self.current_return_type = saved_ret; - self.current_function = saved_fn; - self.positionAt(saved_bb); - return null; - } - - self.current_return_type = ret_sx_type; - 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) { - const fn_stmts = fd.body.data.block.stmts; - for (fn_stmts, 0..) |stmt, stmt_idx| { - // Last statement with xx prefix: use return type as target context - if (stmt_idx == fn_stmts.len - 1 and ret_sx_type != .void_type and - stmt.data == .unary_op and stmt.data.unary_op.op == .xx) - { - last_val = try self.genExprAsType(stmt, ret_sx_type); - } else { - 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 ret_val = try self.prepareReturnValue(val, ret_sx_type); - self.ret(ret_val); - } else { - _ = c.LLVMBuildUnreachable(self.builder); - } - } - - // Restore outer function state - self.named_values = saved_named; - self.narrowed_types = saved_narrowed; - self.current_return_type = saved_ret; - self.current_function = saved_fn; - self.positionAt(saved_bb); - - // Register local function in outer scope's named_values so it - // shadows any top-level function with the same name. - { - var param_types_list = std.ArrayList(Type).empty; - for (fd.params) |param| { - try param_types_list.append(self.allocator, self.resolveType(param.type_expr)); - } - const ret_type_ptr = try self.allocator.create(Type); - ret_type_ptr.* = ret_sx_type; - const fn_ty: Type = .{ .function_type = .{ - .param_types = try param_types_list.toOwnedSlice(self.allocator), - .return_type = ret_type_ptr, - } }; - const local_name_z = try self.allocator.dupeZ(u8, fd.name); - const fn_alloca = self.buildEntryBlockAlloca(self.ptrType(), local_name_z.ptr); - _ = c.LLVMBuildStore(self.builder, function, fn_alloca); - try self.named_values.put(fd.name, .{ .ptr = fn_alloca, .ty = fn_ty }); - } - } - return null; - }, - .struct_decl => |sd| { - try self.registerStructType(sd); - try self.registerStructMethods(sd); - // Method bodies are deferred — they'll be generated via genStructMethodBodies - // in registerStructMethods (non-generic methods are registered with registerFnDecl, - // and their bodies are deferred via shouldDeferFnBody or generated inline) - for (sd.methods) |method_node| { - const fd = method_node.data.fn_decl; - if (fd.type_params.len > 0) continue; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, fd.name }); - try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = sd.name, .source_file = self.current_source_file }); - } - return null; - }, - .union_decl => { - // C-style union — registration handled in type pass - return null; - }, - .protocol_decl => { - // Protocol declarations are handled in the registration pass - return null; - }, - .impl_block => { - // Impl blocks are handled in the registration 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 ret_val = if (self.current_return_type.isOptional()) blk: { - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else if (val_node.data == .unary_op and val_node.data.unary_op.op == .xx) blk: { - // xx in return position: use return type as target context - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else if (self.current_return_type.isClosureType()) blk: { - // Closure return type: provide type context for inferred params - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else blk: { - const raw_val = try self.genExpr(val_node); - break :blk 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; - }, - .push_stmt => |ps| { - return self.genPushStmt(ps); - }, - .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; - } - } - - // Lambda assigned to variable: treat like a local const lambda (create named fn) - if (vd.value) |val| { - if (val.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; - const saved_narrowed2 = self.narrowed_types; - self.named_values = std.StringHashMap(NamedValue).init(self.allocator); - self.narrowed_types = std.StringHashMap(NarrowedInfo).init(self.allocator); - - try self.registerLambdaAsFunction(vd.name, val.data.lambda); - try self.genLambdaBody(vd.name, val.data.lambda); - - self.named_values.deinit(); - self.named_values = saved_named; - self.narrowed_types = saved_narrowed2; - 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 (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}); - } - - // Closure-typed variable - if (sx_ty.isClosureType()) { - const llvm_ty = self.getClosureStructType(); - const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); - - if (vd.value == null) { - // Default: zero-init (null fn_ptr and null env) - self.storeNull(llvm_ty, alloca); - } else if (vd.value.?.data == .undef_literal) { - self.storeUndef(llvm_ty, alloca); - } else if (vd.value.?.data == .struct_literal) { - // .{ fn_ptr = ..., env = ... } — construct closure from anonymous struct literal - const sl = vd.value.?.data.struct_literal; - var fn_ptr_val: ?c.LLVMValueRef = null; - var env_val: ?c.LLVMValueRef = null; - for (sl.field_inits) |fi| { - const fname = fi.name orelse return self.emitError("closure literal fields must be named (.fn_ptr, .env)"); - if (std.mem.eql(u8, fname, "fn_ptr")) { - fn_ptr_val = try self.genExpr(fi.value); - } else if (std.mem.eql(u8, fname, "env")) { - env_val = try self.genExpr(fi.value); - } else { - return self.emitErrorFmt("unknown closure field '{s}' (expected .fn_ptr, .env)", .{fname}); - } - } - const fn_ptr = fn_ptr_val orelse return self.emitError("closure literal missing .fn_ptr field"); - const env = env_val orelse return self.emitError("closure literal missing .env field"); - // Build { fn_ptr, env } aggregate - var closure_val = c.LLVMGetUndef(llvm_ty); - closure_val = self.insertValue(closure_val, fn_ptr, 0, "closure_fn"); - closure_val = self.insertValue(closure_val, env, 1, "closure_env"); - _ = c.LLVMBuildStore(self.builder, closure_val, alloca); - } else { - 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; - } - - // 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 and vd.value.?.data.call.callee.data == .enum_literal) { - // .method(args) — struct static method shorthand with inferred type - const cn = vd.value.?.data.call; - const method_name = cn.callee.data.enum_literal.name; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sname, method_name }); - const val = try self.genCallByName(qualified, cn); - _ = c.LLVMBuildStore(self.builder, val, alloca); - } 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; - } - - // Tuple-typed variable - if (sx_ty.isTuple()) { - const llvm_ty = self.typeToLLVM(sx_ty); - if (vd.value) |val| { - if (val.data == .undef_literal) { - // Zero-initialized tuple - const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); - self.storeNull(llvm_ty, alloca); - try self.registerVariable(vd.name, alloca, sx_ty); - return null; - } - if (val.data == .tuple_literal) { - // Tuple literal — use its alloca directly - const lit_alloca = try self.genTupleLiteral(val.data.tuple_literal); - try self.registerVariable(vd.name, lit_alloca, sx_ty); - return null; - } - // General expression (e.g., function call returning a tuple, or tuple op) - const result = try self.genExpr(val); - // If the result is already a tuple alloca (from concat/repeat/etc), use it directly - if (self.tuple_alloca_types.contains(@intFromPtr(result))) { - try self.registerVariable(vd.name, result, sx_ty); - return null; - } - // Otherwise it's a loaded struct value (e.g., from function call) — store into an alloca - const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); - _ = c.LLVMBuildStore(self.builder, result, alloca); - try self.registerVariable(vd.name, alloca, sx_ty); - return null; - } - return self.emitErrorFmt("tuple variable '{s}' must be initialized", .{vd.name}); - } - - // 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 { - // Allow initialization from expression (e.g. function call returning union) - var val = try self.genExpr(vd.value.?); - val = self.loadIfPointer(val, info.llvm_type, "union_init"); - _ = c.LLVMBuildStore(self.builder, val, alloca); - } - - 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 val = try self.genTaggedEnumLiteral(vd.value.?.data.enum_literal.name, null, uname); - _ = c.LLVMBuildStore(self.builder, val, alloca); - } else if (vd.value.?.data == .call and vd.value.?.data.call.callee.data == .enum_literal) { - // .variant(payload) — tagged enum construction with inferred type - const cn = vd.value.?.data.call; - const payload_node: ?*Node = if (cn.args.len > 0) cn.args[0] else null; - const val = try self.genTaggedEnumLiteral(cn.callee.data.enum_literal.name, payload_node, uname); - _ = c.LLVMBuildStore(self.builder, val, alloca); - } else if (vd.value.?.data == .call) { - // Call returning a union — function call (value) - const result = try self.genExpr(vd.value.?); - _ = c.LLVMBuildStore(self.builder, result, 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 elem_node = al.elements[i]; - const val = try self.genExprAsType(elem_node, elem_sx_ty); - const gep = self.gepArrayElement(llvm_arr_ty, arr_alloca, self.constInt32(@intCast(i)), "arr_elem"); - // Array literals return allocas via genArrayLiteral — load value before storing - if (elem_node.data == .array_literal) { - const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, val, "agg_load"); - _ = c.LLVMBuildStore(self.builder, loaded, gep); - } else { - _ = 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; - } - - // Optional-typed variable - if (sx_ty.isOptional()) { - const llvm_ty = self.typeToLLVM(sx_ty); - const alloca = try self.buildNamedAlloca(llvm_ty, vd.name); - - if (vd.value == null) { - // Default-init: null optional - self.storeNull(llvm_ty, alloca); - } else if (vd.value.?.data == .undef_literal) { - self.storeUndef(llvm_ty, alloca); - } else { - 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; - } - - // 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; - const saved_narrowed2 = self.narrowed_types; - self.named_values = std.StringHashMap(NamedValue).init(self.allocator); - self.narrowed_types = std.StringHashMap(NarrowedInfo).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.narrowed_types = saved_narrowed2; - self.current_return_type = saved_ret; - self.current_function = saved_fn; - self.positionAt(saved_bb); - return null; - } - - // Try comptime evaluation for :: call expressions (all args must be comptime-known) - if (cd.value.data == .call) { - if (self.tryComptimeCallEval(cd)) |result| { - if (result == .string_val) { - const llvm_val = self.comptimeValueToLLVM(result, .string_type); - const llvm_ty = self.getStringStructType(); - const alloca = try self.buildNamedAlloca(llvm_ty, cd.name); - _ = c.LLVMBuildStore(self.builder, llvm_val, alloca); - try self.registerVariable(cd.name, alloca, .string_type); - try self.local_comptime_constants.put(cd.name, result); - return null; - } else if (result == .int_val) { - const llvm_val = self.constInt64(@bitCast(result.int_val)); - const alloca = try self.buildNamedAlloca(self.i64Type(), cd.name); - _ = c.LLVMBuildStore(self.builder, llvm_val, alloca); - try self.registerVariable(cd.name, alloca, Type.s(64)); - try self.local_comptime_constants.put(cd.name, result); - 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; - } - - // Tuple-typed constant: tuple literal returns alloca, use directly - if (sx_ty.isTuple()) { - if (cd.value.data == .tuple_literal) { - const lit_alloca = try self.genTupleLiteral(cd.value.data.tuple_literal); - try self.registerVariable(cd.name, lit_alloca, sx_ty); - return null; - } - // General expression (e.g., function call returning a tuple) - const val = 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, val, alloca); - try self.registerVariable(cd.name, alloca, 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); - - // Track comptime value for :: string/int literals (for comptime format evaluation) - if (cd.value.data == .string_literal) { - const sl = cd.value.data.string_literal; - const content = if (sl.is_raw) sl.raw else unescape.unescapeString(self.allocator, sl.raw) catch return null; - try self.local_comptime_constants.put(cd.name, .{ .string_val = content }); - } else if (cd.value.data == .int_literal) { - try self.local_comptime_constants.put(cd.name, .{ .int_val = cd.value.data.int_literal.value }); - } - - 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"), - .and_assign => c.LLVMBuildAnd(self.builder, cur, rhs, "bandtmp"), - .or_assign => c.LLVMBuildOr(self.builder, cur, rhs, "bortmp"), - .xor_assign => c.LLVMBuildXor(self.builder, cur, rhs, "bxortmp"), - .shl_assign => c.LLVMBuildShl(self.builder, cur, rhs, "shltmp"), - .shr_assign => if (ty.isUnsigned()) c.LLVMBuildLShr(self.builder, cur, rhs, "shrtmp") else c.LLVMBuildAShr(self.builder, cur, rhs, "shrtmp"), - .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; or 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 rhs_val = try self.genExprAsType(asgn.value, pointee_ty); - self.storeOrCompound(asgn.op, ptr_val, rhs_val, pointee_ty, "deref_cur"); - return null; - } - - // Target must be an identifier (or type_expr for names like s2, u8 that match type patterns) - const name = if (asgn.target.data == .identifier) - asgn.target.data.identifier.name - else if (asgn.target.data == .type_expr) - asgn.target.data.type_expr.name - else - return self.emitError("assignment target must be a variable"); - 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}); - - // Kill narrowing on reassignment: if x was narrowed from ?T to T, - // reassignment invalidates the narrowed value - _ = self.narrowed_types.remove(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.genExprAsType(asgn.value, entry.ty); - 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; - - // Non-identifier object (e.g. a.b.c = val — chained field assignment) - if (fa.object.data != .identifier) { - const obj_ty = self.inferType(fa.object); - if (obj_ty.isPointer()) { - // Chained pointer auto-deref: expr.field = val where expr is *Struct - const pointee_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse - return self.emitError("unknown pointee type for chained 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 ptr_val = try self.genExpr(fa.object); - const gep = self.structGEP(info.llvm_type, ptr_val, @intCast(fi), "chain_pfield_ptr"); - const rhs = try self.genExprAsType(asgn.value, field_ty); - self.storeOrCompound(asgn.op, gep, rhs, field_ty, "chain_pcur"); - return null; - } - } - if (obj_ty.isStruct()) { - // Chained struct field: expr.field = val where expr is a struct (e.g. self.inner.len = 0) - const sname = obj_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 intermediate_ptr = try self.genAddressOf(fa.object); - const gep = self.structGEP(info.llvm_type, intermediate_ptr, @intCast(fi), "chain_sfield_ptr"); - const rhs = try self.genExprAsType(asgn.value, field_ty); - self.storeOrCompound(asgn.op, gep, rhs, field_ty, "chain_scur"); - return null; - } - return self.emitError("field assignment target must be a variable"); - } - const obj_name = fa.object.data.identifier.name; - const entry = self.getNamedOrGlobal(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}); - } - - // Slice/string field assignment: s.ptr = val, s.len = val - if (entry.ty == .string_type or entry.ty.isSlice()) { - const struct_ty = self.getStringStructType(); - if (std.mem.eql(u8, fa.field, "ptr")) { - const gep = self.structGEP(struct_ty, entry.ptr, 0, "slice_ptr"); - const elem_name = if (entry.ty == .string_type) "u8" else entry.ty.slice_type.element_name; - const ptr_ty = Type{ .many_pointer_type = .{ .element_name = elem_name } }; - const rhs = try self.genExprAsType(asgn.value, ptr_ty); - _ = c.LLVMBuildStore(self.builder, rhs, gep); - return null; - } - if (std.mem.eql(u8, fa.field, "len")) { - const gep = self.structGEP(struct_ty, entry.ptr, 1, "slice_len"); - const rhs = try self.genExprAsType(asgn.value, Type.s(64)); - _ = c.LLVMBuildStore(self.builder, rhs, gep); - return null; - } - return self.emitErrorFmt("no field '{s}' on slice (available: .ptr, .len)", .{fa.field}); - } - - // Closure field assignment: c.fn_ptr = val, c.env = val - if (entry.ty.isClosureType()) { - const struct_ty = self.getClosureStructType(); - if (std.mem.eql(u8, fa.field, "fn_ptr")) { - const gep = self.structGEP(struct_ty, entry.ptr, 0, "closure_fn_ptr"); - const rhs = try self.genExpr(asgn.value); - _ = c.LLVMBuildStore(self.builder, rhs, gep); - return null; - } - if (std.mem.eql(u8, fa.field, "env")) { - const gep = self.structGEP(struct_ty, entry.ptr, 1, "closure_env"); - const rhs = try self.genExpr(asgn.value); - _ = c.LLVMBuildStore(self.builder, rhs, gep); - return null; - } - return self.emitErrorFmt("no field '{s}' on Closure (available: .fn_ptr, .env)", .{fa.field}); - } - - 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()) { - const arr_info = obj_ty.array_type; - const elem_ty = self.resolveTypeFromName(arr_info.element_name) orelse return self.emitError("unknown array element type"); - 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.genExprAsType(asgn.value, elem_ty); - const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), entry.ptr, idx, "arridx"); - self.storeOrCompound(asgn.op, gep_ptr, val, elem_ty, "arrcur"); - 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.genExprAsType(asgn.value, elem_ty); - const gep_ptr = self.gepArrayElement(self.typeToLLVM(obj_ty), field_ptr, idx, "field_arridx"); - self.storeOrCompound(asgn.op, gep_ptr, val, elem_ty, "farrcur"); - return null; - } - } - if (obj_ty.isSlice()) { - const slice_info = obj_ty.slice_type; - const elem_ty = self.resolveTypeFromName(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| { - // Flow-sensitive narrowing: if variable is narrowed, load from payload alloca - if (self.narrowed_types.get(ident.name)) |ni| { - return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ni.narrowed_ty), ni.payload_ptr, "narrow_val"); - } - 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)); - } - } - // Search all registered namespaces (for cross-namespace references) - if (fn_val == null) { - if (self.findFunction(ident.name)) |found| fn_val = found; - } - // Foreign rename fallback: sx name → C symbol name - if (fn_val == null) { - if (self.foreign_name_map.get(ident.name)) |c_name| { - var rbuf: [256]u8 = undefined; - fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf)); - } - } - 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); - - // Optional-null comparison: x == null / x != null - if (binop.op == .eq or binop.op == .neq) { - const lhs_ty = self.inferType(binop.lhs); - const rhs_ty = self.inferType(binop.rhs); - const opt_side: ?struct { expr: *Node, ty: Type } = - if (lhs_ty.isOptional() and binop.rhs.data == .null_literal) - .{ .expr = binop.lhs, .ty = lhs_ty } - else if (rhs_ty.isOptional() and binop.lhs.data == .null_literal) - .{ .expr = binop.rhs, .ty = rhs_ty } - else - null; - if (opt_side) |os| { - const opt_val = try self.genExpr(os.expr); - const has_val = self.optionalHasValue(opt_val, os.ty); - // == null → NOT has_value; != null → has_value - if (binop.op == .eq) { - return c.LLVMBuildNot(self.builder, has_val, "opt_is_null"); - } - return has_val; - } - } - - 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"); - - 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"); - const pred: c_uint = if (binop.op == .eq) c.LLVMIntEQ else c.LLVMIntNE; - return self.icmp(pred, lhs_tag, rhs_tag, "tag_cmp"); - } - - // String comparison: compare lengths, then memcmp content - if ((result_type == .string_type or result_type.isSlice()) and (binop.op == .eq or binop.op == .neq)) { - const lhs = try self.genExpr(binop.lhs); - const rhs = try self.genExpr(binop.rhs); - return self.genStringComparison(binop.op, lhs, rhs); - } - - // Single-element tuple vs scalar: unwrap (T,) → T - { - const unwrap_lhs = lhs_ty.isTuple() and lhs_ty.tuple_type.field_types.len == 1 and !rhs_ty.isTuple(); - const unwrap_rhs = rhs_ty.isTuple() and rhs_ty.tuple_type.field_types.len == 1 and !lhs_ty.isTuple(); - if (unwrap_lhs or unwrap_rhs) { - const eff_lhs_ty = if (unwrap_lhs) lhs_ty.tuple_type.field_types[0] else lhs_ty; - const eff_rhs_ty = if (unwrap_rhs) rhs_ty.tuple_type.field_types[0] else rhs_ty; - const cmp_type = Type.widen(eff_lhs_ty, eff_rhs_ty); - - var lhs_val: c.LLVMValueRef = undefined; - if (unwrap_lhs) { - const alloca = try self.genExpr(binop.lhs); - const elem_llvm = self.typeToLLVM(eff_lhs_ty); - var ftypes = [_]c.LLVMTypeRef{elem_llvm}; - const tup_llvm = c.LLVMStructTypeInContext(self.context, &ftypes, 1, 0); - const gep = self.structGEP(tup_llvm, alloca, 0, "unwrap_l"); - const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm, gep, "elem_l"); - lhs_val = self.convertValue(loaded, eff_lhs_ty, cmp_type); - } else { - lhs_val = try self.genExprAsType(binop.lhs, cmp_type); - } - - var rhs_val: c.LLVMValueRef = undefined; - if (unwrap_rhs) { - const alloca = try self.genExpr(binop.rhs); - const elem_llvm = self.typeToLLVM(eff_rhs_ty); - var ftypes = [_]c.LLVMTypeRef{elem_llvm}; - const tup_llvm = c.LLVMStructTypeInContext(self.context, &ftypes, 1, 0); - const gep = self.structGEP(tup_llvm, alloca, 0, "unwrap_r"); - const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm, gep, "elem_r"); - rhs_val = self.convertValue(loaded, eff_rhs_ty, cmp_type); - } else { - rhs_val = try self.genExprAsType(binop.rhs, cmp_type); - } - - return self.genBinaryOp(binop.op, lhs_val, rhs_val, cmp_type); - } - } - - // Tuple comparison: element-wise - if (lhs_ty.isTuple() and rhs_ty.isTuple() and - (binop.op == .eq or binop.op == .neq or binop.op == .lt or binop.op == .lte or binop.op == .gt or binop.op == .gte)) - { - return self.genTupleComparison(binop.op, binop.lhs, binop.rhs, lhs_ty, rhs_ty); - } - - // Tuple concatenation: tuple + tuple - if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) { - return self.genTupleConcat(binop.lhs, binop.rhs, lhs_ty, rhs_ty); - } - - // Tuple repetition: tuple * int - if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) { - return self.genTupleRepeat(binop.lhs, binop.rhs, lhs_ty); - } - - // Membership: value in tuple - if (binop.op == .in_op) { - if (rhs_ty.isTuple()) { - return self.genTupleMembership(binop.lhs, binop.rhs, rhs_ty); - } - return self.emitError("'in' requires a tuple on the right side"); - } - - 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"), - .bit_not => c.LLVMBuildNot(self.builder, operand, "bnottmp"), - .xx, .address_of => unreachable, - }; - }, - .enum_literal => |el| { - if (self.current_return_type.isUnion()) { - return self.genTaggedEnumLiteral(el.name, null, 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); - }, - .tuple_literal => |tl| { - return self.genTupleLiteral(tl); - }, - .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| { - if (fa.is_optional) { - return self.genOptionalChain(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); - }, - .ufcs_alias => |ua| { - try self.ufcs_aliases.put(ua.name, ua.target); - return null; - }, - .assignment => |asgn| { - return self.genAssignment(asgn); - }, - .multi_assign => |ma| { - return self.genMultiAssign(ma); - }, - .return_stmt => |rs| { - if (rs.value) |val_node| { - const ret_val = if (self.current_return_type.isOptional()) blk: { - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else if (val_node.data == .unary_op and val_node.data.unary_op.op == .xx) blk: { - // xx in return position: use return type as target context - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else if (self.current_return_type.isClosureType()) blk: { - // Closure return type: provide type context for inferred params - break :blk try self.genExprAsType(val_node, self.current_return_type); - } else blk: { - const raw_val = try self.genExpr(val_node); - break :blk 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; - }, - .null_coalesce => |nc| { - const opt_val = try self.genExpr(nc.lhs); - const opt_ty = self.inferType(nc.lhs); - if (!opt_ty.isOptional()) return self.emitError("'??' requires an optional type on the left side"); - - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse - return self.emitError("unknown optional inner type"); - - const has_val = self.optionalHasValue(opt_val, opt_ty); - const then_bb = self.appendBB("coalesce_some"); - const else_bb = self.appendBB("coalesce_none"); - const merge_bb = self.appendBB("coalesce_merge"); - _ = c.LLVMBuildCondBr(self.builder, has_val, then_bb, else_bb); - - // Some path: extract payload - c.LLVMPositionBuilderAtEnd(self.builder, then_bb); - const payload = self.optionalPayload(opt_val, opt_ty); - const then_end_bb = c.LLVMGetInsertBlock(self.builder); - _ = c.LLVMBuildBr(self.builder, merge_bb); - - // None path: evaluate default - c.LLVMPositionBuilderAtEnd(self.builder, else_bb); - const default_val = try self.genExprAsType(nc.rhs, child_ty); - const else_end_bb = c.LLVMGetInsertBlock(self.builder); - _ = c.LLVMBuildBr(self.builder, merge_bb); - - // Merge with PHI - c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); - const llvm_child_ty = self.typeToLLVM(child_ty); - const phi = c.LLVMBuildPhi(self.builder, llvm_child_ty, "coalesce"); - var vals = [2]c.LLVMValueRef{ payload, default_val }; - var bbs = [2]c.LLVMBasicBlockRef{ then_end_bb, else_end_bb }; - c.LLVMAddIncoming(phi, &vals, &bbs, 2); - return phi; - }, - .force_unwrap => |fu| { - const opt_val = try self.genExpr(fu.operand); - const opt_ty = self.inferType(fu.operand); - if (!opt_ty.isOptional()) return self.emitError("force unwrap (!) requires an optional type"); - - // Check has_value — if false, trap - const has_val = self.optionalHasValue(opt_val, opt_ty); - const then_bb = self.appendBB("unwrap_ok"); - const trap_bb = self.appendBB("unwrap_trap"); - _ = c.LLVMBuildCondBr(self.builder, has_val, then_bb, trap_bb); - - // Trap block: call llvm.trap + unreachable - c.LLVMPositionBuilderAtEnd(self.builder, trap_bb); - const trap_fn = c.LLVMGetIntrinsicDeclaration(self.module, c.LLVMLookupIntrinsicID("llvm.trap", 9), null, 0); - _ = c.LLVMBuildCall2(self.builder, c.LLVMFunctionType(self.voidType(), null, 0, 0), trap_fn, null, 0, ""); - _ = c.LLVMBuildUnreachable(self.builder); - - // OK block: extract payload - c.LLVMPositionBuilderAtEnd(self.builder, then_bb); - return self.optionalPayload(opt_val, opt_ty); - }, - .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); - }, - .type_expr => |te| { - // type_expr can appear when a variable name matches a type (e.g. s2, u8) - // Fall back to identifier behavior: check named_values first - if (self.lookupValue(te.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"), - } - } - return self.emitErrorFmt("type '{s}' used as expression", .{te.name}); - }, - 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"); - } - if (obj_ty.isManyPointer()) { - const raw_ptr = try self.genExpr(ie.object); - const elem_ty = self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse Type.u(8); - return self.gepPointerElement(self.typeToLLVM(elem_ty), raw_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.getNamedOrGlobal(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"); - } - } - } - } else { - // Chained field access: a.b.c — recursively get address of intermediate - const intermediate_ptr = try self.genAddressOf(fa.object); - const obj_ty = self.inferType(fa.object); - if (obj_ty.isStruct()) { - const sname = obj_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, intermediate_ptr, @intCast(idx), "addr_chain_field"); - } - if (obj_ty.isPointer()) { - const pointee_name = obj_ty.pointer_type.pointee_name; - if (self.lookupStructInfo(pointee_name)) |info| { - const loaded_ptr = c.LLVMBuildLoad2(self.builder, - self.ptrType(), intermediate_ptr, "chain_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_chain_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 { - return self.buildStructFieldsWithType(name, field_type_nodes, null); - } - - fn buildStructFieldsWithType(self: *CodeGen, name: []const u8, field_type_nodes: []const *Node, existing_llvm_type: ?c.LLVMTypeRef) !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 struct_ty = existing_llvm_type orelse blk: { - const name_z = try self.allocator.dupeZ(u8, name); - break :blk 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 => |*sd| { - sd.name = synthetic_name; - try self.registerStructType(sd.*); - }, - .union_decl => |*ud| { - ud.name = synthetic_name; - try self.registerUnionType(ud.*); - }, - .enum_decl => |*ed| { - ed.name = synthetic_name; - if (ed.variant_types.len > 0) { - try self.registerTaggedEnum(ed.*); - } else { - try self.type_registry.put(synthetic_name, .{ .plain_enum = ed.variant_names }); - _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); - if (ed.backing_type) |bt_node| { - const bt = self.resolveType(bt_node); - try self.enum_backing_types.put(synthetic_name, self.typeToLLVM(bt)); - } - } - }, - else => {}, - } - } - - /// Expand #using entries: interleave used struct fields with declared fields. - fn expandStructUsing(self: *CodeGen, sd: ast.StructDecl, existing_llvm_type: ?c.LLVMTypeRef) !StructInfo { - var all_names = std.ArrayList([]const u8).empty; - var all_types = std.ArrayList(Type).empty; - var all_defaults = std.ArrayList(?*Node).empty; - - var using_idx: usize = 0; - for (0..sd.field_names.len + 1) |i| { - // Splice #using entries whose insert_index matches current position - while (using_idx < sd.using_entries.len and - sd.using_entries[using_idx].insert_index == i) - { - const entry = sd.using_entries[using_idx]; - const used_name = self.resolveAlias(entry.type_name); - const used_info = self.lookupStructInfo(used_name) orelse - return self.emitErrorFmt("#using: struct '{s}' not found (declared before '{s}'?)", .{ entry.type_name, sd.name }); - - for (used_info.field_names, 0..) |fname, fi| { - try all_names.append(self.allocator, fname); - try all_types.append(self.allocator, used_info.field_types[fi]); - try all_defaults.append(self.allocator, used_info.field_defaults[fi]); - } - using_idx += 1; - } - // Append own field at position i - if (i < sd.field_names.len) { - try all_names.append(self.allocator, sd.field_names[i]); - try all_types.append(self.allocator, self.resolveType(sd.field_types[i])); - try all_defaults.append(self.allocator, sd.field_defaults[i]); - } - } - - // Build LLVM struct type with expanded fields - var llvm_types = std.ArrayList(c.LLVMTypeRef).empty; - for (all_types.items) |ty| { - try llvm_types.append(self.allocator, self.typeToLLVM(ty)); - } - const struct_ty = existing_llvm_type orelse blk: { - const name_z = try self.allocator.dupeZ(u8, sd.name); - break :blk c.LLVMStructCreateNamed(self.context, name_z.ptr); - }; - const llvm_slice = try llvm_types.toOwnedSlice(self.allocator); - c.LLVMStructSetBody(struct_ty, if (llvm_slice.len > 0) llvm_slice.ptr else null, @intCast(llvm_slice.len), 0); - - return StructInfo{ - .field_names = try all_names.toOwnedSlice(self.allocator), - .field_types = try all_types.toOwnedSlice(self.allocator), - .field_defaults = try all_defaults.toOwnedSlice(self.allocator), - .llvm_type = struct_ty, - }; - } - - /// Register a struct type name with an opaque LLVM struct (no fields yet). - /// Called during registerTypes phase so other types can reference this name. - fn registerStructName(self: *CodeGen, sd: ast.StructDecl) anyerror!void { - if (sd.type_params.len > 0) { - try self.generic_struct_templates.put(sd.name, sd); - return; - } - - // Create opaque named LLVM struct (body set later in resolveStructFields) - const name_z = try self.allocator.dupeZ(u8, sd.name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - - try self.type_registry.put(sd.name, .{ .struct_info = .{ - .field_names = &.{}, - .field_types = &.{}, - .field_defaults = &.{}, - .llvm_type = struct_ty, - } }); - - // Hoist inline type declarations (anonymous struct/union/enum in field types) - for (sd.field_types, 0..) |ft, i| { - try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft); - } - } - - /// Resolve struct field types and set the LLVM struct body. - /// Called during resolveFields phase after all type names are registered. - fn resolveStructFields(self: *CodeGen, sd: ast.StructDecl) anyerror!void { - if (sd.type_params.len > 0) return; - - const existing = self.lookupStructInfo(sd.name) orelse return; - const struct_ty = existing.llvm_type orelse return; - - const sinfo = if (sd.using_entries.len > 0) - try self.expandStructUsing(sd, struct_ty) - else blk: { - const build = try self.buildStructFieldsWithType(sd.name, sd.field_types, struct_ty); - - // 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; - } - } - - break :blk 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 }); - } - - /// Convenience: register name + resolve fields in one call. - /// Used by hoistInlineTypeDecl and resolveEnumLayout for on-demand type creation. - fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { - try self.registerStructName(sd); - try self.resolveStructFields(sd); - } - - fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { - try self.registerUnionName(ud); - try self.resolveUnionFields(ud); - } - - fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void { - try self.registerTaggedEnumName(ud); - try self.resolveTaggedEnumFields(ud); - } - - /// Register methods declared inside a struct body. - /// For generic structs, method type_params are augmented with the struct's type params. - fn registerStructMethods(self: *CodeGen, sd: ast.StructDecl) !void { - if (sd.methods.len == 0) return; - - // Register struct name as a namespace for static calls: StructName.method() - try self.namespaces.put(sd.name, {}); - - for (sd.methods) |method_node| { - const fd = method_node.data.fn_decl; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, fd.name }); - - if (sd.type_params.len > 0) { - // Generic struct: merge struct type params into method, store as generic template. - // This allows List($T).append to be resolved as List.append with T inferred from args. - var merged_params = std.ArrayList(ast.StructTypeParam).empty; - // Add struct's type params first - for (sd.type_params) |tp| try merged_params.append(self.allocator, tp); - // Add method's own type params (if any), avoiding duplicates - for (fd.type_params) |tp| { - var dup = false; - for (sd.type_params) |stp| { - if (std.mem.eql(u8, stp.name, tp.name)) { dup = true; break; } - } - if (!dup) try merged_params.append(self.allocator, tp); - } - const augmented_fd = ast.FnDecl{ - .name = fd.name, - .params = fd.params, - .return_type = fd.return_type, - .body = fd.body, - .type_params = try merged_params.toOwnedSlice(self.allocator), - .is_arrow = fd.is_arrow, - }; - try self.generic_templates.put(qualified, augmented_fd); - } else if (fd.type_params.len > 0) { - // Non-generic struct with generic method - try self.generic_templates.put(qualified, fd); - } else { - // Non-generic struct, non-generic method: register directly - _ = try self.registerFnDecl(fd, qualified); - } - } - } - - fn registerStructConstants(self: *CodeGen, sd: ast.StructDecl) !void { - if (sd.constants.len == 0) return; - - try self.namespaces.put(sd.name, {}); - - for (sd.constants) |const_node| { - const cd = const_node.data.const_decl; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, cd.name }); - - // Reuse the same registration logic as top-level constants - const sx_ty = if (cd.type_annotation) |ta| - self.resolveType(ta) - else - self.inferType(cd.value); - if (sx_ty == .void_type) continue; - - const const_val = self.evalConstant(cd.value, sx_ty) orelse continue; - - const name_z = try self.allocator.dupeZ(u8, qualified); - 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(qualified, .{ - .global = global, - .ty = sx_ty, - .expr = cd.value, - .is_resolved = true, - }); - } - } - - /// Register a protocol declaration. For #inline protocols, this generates - /// a struct type with ctx + fn-ptr fields and wrapper methods. - fn registerProtocolDecl(self: *CodeGen, pd: ast.ProtocolDecl) !void { - try self.protocol_decls.put(pd.name, pd); - - // Skip if already registered (can happen with diamond imports) - if (self.type_registry.contains(pd.name)) return; - - if (pd.is_inline) { - // #inline protocol: generate struct { ctx: *void, method1: fn_ptr, method2: fn_ptr, ... } - const n_fields = 1 + pd.methods.len; // ctx + one fn-ptr per method - var field_names = try self.allocator.alloc([]const u8, n_fields); - var field_types = try self.allocator.alloc(Type, n_fields); - var field_defaults = try self.allocator.alloc(?*Node, n_fields); - var llvm_field_types = try self.allocator.alloc(c.LLVMTypeRef, n_fields); - - // First field: ctx: *void - field_names[0] = "ctx"; - field_types[0] = .{ .pointer_type = .{ .pointee_name = "void" } }; - field_defaults[0] = null; - llvm_field_types[0] = self.ptrType(); - - // Fn-ptr fields: one per protocol method, with *void prepended as first param - for (pd.methods, 0..) |method, i| { - field_names[1 + i] = method.name; - field_defaults[1 + i] = null; - llvm_field_types[1 + i] = self.ptrType(); // fn ptrs are opaque pointers - - // Build the fn-ptr Type: (*void, param_types...) -> return_type - var fn_params = std.ArrayList(Type).empty; - try fn_params.append(self.allocator, .{ .pointer_type = .{ .pointee_name = "void" } }); // ctx param - for (method.params) |param_node| { - try fn_params.append(self.allocator, self.resolveType(param_node)); - } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else Type.void_type; - const ret_ptr = try self.allocator.create(Type); - ret_ptr.* = ret_ty; - field_types[1 + i] = .{ .function_type = .{ - .param_types = try fn_params.toOwnedSlice(self.allocator), - .return_type = ret_ptr, - } }; - } - - // Create LLVM struct type - const name_z = try self.allocator.dupeZ(u8, pd.name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - c.LLVMStructSetBody(struct_ty, llvm_field_types.ptr, @intCast(n_fields), 0); - - // Register as struct in type_registry - try self.type_registry.put(pd.name, .{ .struct_info = .{ - .field_names = field_names, - .field_types = field_types, - .field_defaults = field_defaults, - .llvm_type = struct_ty, - } }); - _ = try self.getAnyTypeId(pd.name, .{ .struct_type = pd.name }); - - // Register as namespace for wrapper methods - try self.namespaces.put(pd.name, {}); - - // Generate wrapper methods: Protocol.method(self: Protocol, args...) -> R - // These extract ctx and fn-ptr from the struct, then call fn-ptr(ctx, args...) - for (pd.methods, 0..) |method, i| { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ pd.name, method.name }); - - // Build the wrapper function type: (Protocol, param_types...) -> ret_type - var wrapper_param_types = std.ArrayList(c.LLVMTypeRef).empty; - try wrapper_param_types.append(self.allocator, struct_ty); // self as Protocol value - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try wrapper_param_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else Type.void_type; - const llvm_ret = self.typeToLLVM(ret_ty); - const wrapper_params = try wrapper_param_types.toOwnedSlice(self.allocator); - - const fn_type = c.LLVMFunctionType(llvm_ret, if (wrapper_params.len > 0) wrapper_params.ptr else null, @intCast(wrapper_params.len), 0); - const qualified_z = try self.allocator.dupeZ(u8, qualified); - const wrapper_fn = c.LLVMAddFunction(self.module, qualified_z.ptr, fn_type); - - // Generate wrapper body: - // entry: - // %self = param[0] - // %ctx = extractvalue %self, 0 - // %fn_ptr = extractvalue %self, 1+i - // %result = call %fn_ptr(%ctx, param[1], param[2], ...) - // ret %result - const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, wrapper_fn, "entry"); - const saved_builder_pos = c.LLVMGetInsertBlock(self.builder); - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - - const self_val = c.LLVMGetParam(wrapper_fn, 0); - const ctx_val = c.LLVMBuildExtractValue(self.builder, self_val, 0, "ctx"); - const fn_ptr_val = c.LLVMBuildExtractValue(self.builder, self_val, @intCast(1 + i), "fn_ptr"); - - // Build call args: ctx, then forwarded params - var call_args = std.ArrayList(c.LLVMValueRef).empty; - try call_args.append(self.allocator, ctx_val); - for (0..method.params.len) |pi| { - try call_args.append(self.allocator, c.LLVMGetParam(wrapper_fn, @intCast(1 + pi))); - } - const call_args_slice = try call_args.toOwnedSlice(self.allocator); - - // Build the LLVM function type for the indirect call - var inner_param_types = std.ArrayList(c.LLVMTypeRef).empty; - try inner_param_types.append(self.allocator, self.ptrType()); // ctx: *void - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try inner_param_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - const inner_params = try inner_param_types.toOwnedSlice(self.allocator); - const inner_fn_type = c.LLVMFunctionType(llvm_ret, if (inner_params.len > 0) inner_params.ptr else null, @intCast(inner_params.len), 0); - - const call_result = c.LLVMBuildCall2(self.builder, inner_fn_type, fn_ptr_val, if (call_args_slice.len > 0) call_args_slice.ptr else null, @intCast(call_args_slice.len), if (ret_ty == .void_type) "" else "result"); - - if (ret_ty == .void_type) { - _ = c.LLVMBuildRetVoid(self.builder); - } else { - _ = c.LLVMBuildRet(self.builder, call_result); - } - - // Restore builder position - if (saved_builder_pos != null) { - c.LLVMPositionBuilderAtEnd(self.builder, saved_builder_pos); - } - - // Store return type for this wrapper - try self.function_return_types.put(qualified, ret_ty); - - // Store sx param types for wrapper (Protocol value + method params) - // Self params are erased to *void in the wrapper signature - { - var sx_params = std.ArrayList(Type).empty; - try sx_params.append(self.allocator, .{ .struct_type = pd.name }); // first param = protocol value - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try sx_params.append(self.allocator, sx_ty); - } - try self.fn_param_types.put(qualified, try sx_params.toOwnedSlice(self.allocator)); - } - } - } else { - // Default (vtable pointer) protocol layout: - // 1. Generate __Vtable struct: { fn1: (*void, ...) -> R, fn2: ... } - // 2. Generate Protocol struct: { ctx: *void, __vtable: *__Vtable } - // 3. Generate wrapper methods that dispatch through vtable - - // === 1. Generate vtable struct === - const vtable_name = try std.fmt.allocPrint(self.allocator, "{s}.__Vtable", .{pd.name}); - { - var vt_field_names = try self.allocator.alloc([]const u8, pd.methods.len); - var vt_field_types = try self.allocator.alloc(Type, pd.methods.len); - var vt_field_defaults = try self.allocator.alloc(?*Node, pd.methods.len); - var vt_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, pd.methods.len); - - for (pd.methods, 0..) |method, i| { - vt_field_names[i] = method.name; - vt_field_defaults[i] = null; - vt_llvm_types[i] = self.ptrType(); // fn ptrs are opaque pointers - - // Build fn-ptr Type: (*void, param_types...) -> return_type - var fn_params = std.ArrayList(Type).empty; - try fn_params.append(self.allocator, .{ .pointer_type = .{ .pointee_name = "void" } }); - for (method.params) |param_node| { - try fn_params.append(self.allocator, self.resolveType(param_node)); - } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else Type.void_type; - const ret_ptr = try self.allocator.create(Type); - ret_ptr.* = ret_ty; - vt_field_types[i] = .{ .function_type = .{ - .param_types = try fn_params.toOwnedSlice(self.allocator), - .return_type = ret_ptr, - } }; - } - - const vt_name_z = try self.allocator.dupeZ(u8, vtable_name); - const vt_struct_ty = c.LLVMStructCreateNamed(self.context, vt_name_z.ptr); - c.LLVMStructSetBody(vt_struct_ty, vt_llvm_types.ptr, @intCast(pd.methods.len), 0); - - try self.type_registry.put(vtable_name, .{ .struct_info = .{ - .field_names = vt_field_names, - .field_types = vt_field_types, - .field_defaults = vt_field_defaults, - .llvm_type = vt_struct_ty, - } }); - } - - // === 2. Generate protocol value struct: { ctx: *void, __vtable: *__Vtable } === - { - const field_names = try self.allocator.alloc([]const u8, 2); - var field_types_arr = try self.allocator.alloc(Type, 2); - var field_defaults_arr = try self.allocator.alloc(?*Node, 2); - var llvm_field_types_arr = try self.allocator.alloc(c.LLVMTypeRef, 2); - - field_names[0] = "ctx"; - field_types_arr[0] = .{ .pointer_type = .{ .pointee_name = "void" } }; - field_defaults_arr[0] = null; - llvm_field_types_arr[0] = self.ptrType(); - - field_names[1] = "__vtable"; - field_types_arr[1] = .{ .pointer_type = .{ .pointee_name = vtable_name } }; - field_defaults_arr[1] = null; - llvm_field_types_arr[1] = self.ptrType(); - - const name_z = try self.allocator.dupeZ(u8, pd.name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - c.LLVMStructSetBody(struct_ty, llvm_field_types_arr.ptr, 2, 0); - - try self.type_registry.put(pd.name, .{ .struct_info = .{ - .field_names = field_names, - .field_types = field_types_arr, - .field_defaults = field_defaults_arr, - .llvm_type = struct_ty, - } }); - _ = try self.getAnyTypeId(pd.name, .{ .struct_type = pd.name }); - } - - // Register as namespace - try self.namespaces.put(pd.name, {}); - - // === 3. Generate wrapper methods (dispatch through vtable pointer) === - const proto_info = self.lookupStructInfo(pd.name) orelse return; - const proto_llvm_ty = proto_info.llvm_type orelse return; - const vtable_info = self.lookupStructInfo(vtable_name) orelse return; - const vtable_llvm_ty = vtable_info.llvm_type orelse return; - - for (pd.methods, 0..) |method, i| { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ pd.name, method.name }); - - // Wrapper sig: (Protocol, param_types...) -> ret_type - var wrapper_param_types = std.ArrayList(c.LLVMTypeRef).empty; - try wrapper_param_types.append(self.allocator, proto_llvm_ty); - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try wrapper_param_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else Type.void_type; - const llvm_ret = self.typeToLLVM(ret_ty); - const wrapper_params = try wrapper_param_types.toOwnedSlice(self.allocator); - - const fn_type = c.LLVMFunctionType(llvm_ret, if (wrapper_params.len > 0) wrapper_params.ptr else null, @intCast(wrapper_params.len), 0); - const qualified_z = try self.allocator.dupeZ(u8, qualified); - const wrapper_fn = c.LLVMAddFunction(self.module, qualified_z.ptr, fn_type); - - // Generate wrapper body: - // %self = param[0] (Protocol struct value) - // %ctx = extractvalue %self, 0 (ctx: *void) - // %vtable_ptr = extractvalue %self, 1 (__vtable: *__Vtable) - // %vtable = load *__Vtable, %vtable_ptr - // %fn_ptr = extractvalue %vtable, i - // %result = call %fn_ptr(%ctx, args...) - const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, wrapper_fn, "entry"); - const saved_builder_pos = c.LLVMGetInsertBlock(self.builder); - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - - const self_val = c.LLVMGetParam(wrapper_fn, 0); - const ctx_val = c.LLVMBuildExtractValue(self.builder, self_val, 0, "ctx"); - const vtable_ptr = c.LLVMBuildExtractValue(self.builder, self_val, 1, "vtable_ptr"); - const vtable_val = c.LLVMBuildLoad2(self.builder, vtable_llvm_ty, vtable_ptr, "vtable"); - const fn_ptr_val = c.LLVMBuildExtractValue(self.builder, vtable_val, @intCast(i), "fn_ptr"); - - var call_args = std.ArrayList(c.LLVMValueRef).empty; - try call_args.append(self.allocator, ctx_val); - for (0..method.params.len) |pi| { - try call_args.append(self.allocator, c.LLVMGetParam(wrapper_fn, @intCast(1 + pi))); - } - const call_args_slice = try call_args.toOwnedSlice(self.allocator); - - var inner_param_types = std.ArrayList(c.LLVMTypeRef).empty; - try inner_param_types.append(self.allocator, self.ptrType()); - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try inner_param_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - const inner_params = try inner_param_types.toOwnedSlice(self.allocator); - const inner_fn_type = c.LLVMFunctionType(llvm_ret, if (inner_params.len > 0) inner_params.ptr else null, @intCast(inner_params.len), 0); - - const call_result = c.LLVMBuildCall2(self.builder, inner_fn_type, fn_ptr_val, if (call_args_slice.len > 0) call_args_slice.ptr else null, @intCast(call_args_slice.len), if (ret_ty == .void_type) "" else "result"); - - if (ret_ty == .void_type) { - _ = c.LLVMBuildRetVoid(self.builder); - } else { - _ = c.LLVMBuildRet(self.builder, call_result); - } - - if (saved_builder_pos != null) { - c.LLVMPositionBuilderAtEnd(self.builder, saved_builder_pos); - } - - try self.function_return_types.put(qualified, ret_ty); - - // Store sx param types for wrapper (Protocol value + method params) - { - var sx_params = std.ArrayList(Type).empty; - try sx_params.append(self.allocator, .{ .struct_type = pd.name }); // first param = protocol value - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try sx_params.append(self.allocator, sx_ty); - } - try self.fn_param_types.put(qualified, try sx_params.toOwnedSlice(self.allocator)); - } - } - } - } - - /// Register an `impl Protocol for Type { methods }` block. - /// Methods are registered as qualified `TypeName.method` (same as struct methods), - /// making them callable via dot-syntax on concrete types (static dispatch). - fn registerImplBlock(self: *CodeGen, ib: ast.ImplBlock) !void { - // Store the impl block keyed by "Protocol\x00Type" - const key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ ib.protocol_name, ib.target_type }); - try self.impl_blocks.put(key, ib); - - // Register target type as a namespace for static calls: TypeName.method() - try self.namespaces.put(ib.target_type, {}); - - for (ib.methods) |method_node| { - const fd = method_node.data.fn_decl; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, fd.name }); - - if (ib.target_type_params.len > 0) { - // Generic target type: merge type params into method, store as generic template - var merged_params = std.ArrayList(ast.StructTypeParam).empty; - for (ib.target_type_params) |tp| try merged_params.append(self.allocator, tp); - for (fd.type_params) |tp| { - var dup = false; - for (ib.target_type_params) |stp| { - if (std.mem.eql(u8, stp.name, tp.name)) { dup = true; break; } - } - if (!dup) try merged_params.append(self.allocator, tp); - } - const augmented_fd = ast.FnDecl{ - .name = fd.name, - .params = fd.params, - .return_type = fd.return_type, - .body = fd.body, - .type_params = try merged_params.toOwnedSlice(self.allocator), - .is_arrow = fd.is_arrow, - }; - try self.generic_templates.put(qualified, augmented_fd); - } else if (fd.type_params.len > 0) { - // Non-generic target with generic method - try self.generic_templates.put(qualified, fd); - } else { - // Non-generic: register directly - _ = try self.registerFnDecl(fd, qualified); - } - try self.fn_signatures.put(qualified, self.buildFnSignature(fd)); - } - - // Synthesize default method implementations for unoverridden protocol methods - if (self.protocol_decls.get(ib.protocol_name)) |pd| { - for (pd.methods) |method| { - if (method.default_body == null) continue; - - // Check if the impl already overrides this method - var overridden = false; - for (ib.methods) |m| { - if (std.mem.eql(u8, m.data.fn_decl.name, method.name)) { - overridden = true; - break; - } - } - if (overridden) continue; - - // Synthesize a fn_decl: method_name :: (self: *ConcreteType, params...) -> R { default_body } - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, method.name }); - const self_fd = try self.synthesizeDefaultMethod(ib.target_type, method); - _ = try self.registerFnDecl(self_fd, qualified); - try self.fn_signatures.put(qualified, self.buildFnSignature(self_fd)); - } - } - - // Generate thunks (and vtable constants for non-inline protocols) - if (self.protocol_decls.get(ib.protocol_name)) |pd| { - try self.generateProtocolThunks(pd, ib); - } - } - - /// Generate thunk functions for a protocol impl. - /// Each thunk has signature (ctx: *void, args...) -> R and calls Type.method(xx ctx, args...). - fn generateProtocolThunks(self: *CodeGen, pd: ast.ProtocolDecl, ib: ast.ImplBlock) !void { - var thunks = try self.allocator.alloc(c.LLVMValueRef, pd.methods.len); - - for (pd.methods, 0..) |method, i| { - const thunk_name = try std.fmt.allocPrint(self.allocator, "__{s}_{s}_{s}", .{ ib.target_type, pd.name, method.name }); - const thunk_name_z = try self.allocator.dupeZ(u8, thunk_name); - - // Thunk signature: (ctx: *void, method_params...) -> method_return_type - var param_types = std.ArrayList(c.LLVMTypeRef).empty; - try param_types.append(self.allocator, self.ptrType()); // ctx: *void - for (method.params) |param_node| { - const sx_ty = self.resolveType(param_node); - try param_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - const param_slice = try param_types.toOwnedSlice(self.allocator); - const ret_ty = if (method.return_type) |rt| self.resolveType(rt) else Type.void_type; - const llvm_ret = self.typeToLLVM(ret_ty); - - const fn_type = c.LLVMFunctionType(llvm_ret, if (param_slice.len > 0) param_slice.ptr else null, @intCast(param_slice.len), 0); - const thunk_fn = c.LLVMAddFunction(self.module, thunk_name_z.ptr, fn_type); - c.LLVMSetLinkage(thunk_fn, c.LLVMPrivateLinkage); - - // Generate thunk body: - // entry: - // %ctx = param[0] (raw *void) - // %self = bitcast %ctx to *TargetType (no-op in opaque ptr world) - // %result = call TargetType.method(%self, param[1], param[2], ...) - // ret %result - const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, thunk_fn, "entry"); - const saved_pos = c.LLVMGetInsertBlock(self.builder); - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - - // Look up the actual impl method - const impl_method_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, method.name }); - var nbuf: [256]u8 = undefined; - const impl_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(impl_method_name, &nbuf)); - - if (impl_fn != null) { - // Build call args: ctx (as *Type, but opaque ptr = no cast needed), then forwarded params - var call_args = std.ArrayList(c.LLVMValueRef).empty; - try call_args.append(self.allocator, c.LLVMGetParam(thunk_fn, 0)); // ctx as *Type - for (method.params, 0..) |param_node, pi| { - const thunk_param = c.LLVMGetParam(thunk_fn, @intCast(1 + pi)); - // Check if this param is Self (erased to *void in thunk) - if (param_node.data == .type_expr and std.mem.eql(u8, param_node.data.type_expr.name, "Self")) { - // Self param: thunk receives *void (pointer to concrete value) - // Impl method expects concrete value by-value — load it - const concrete_ty_name = ib.target_type; - if (self.lookupStructInfo(concrete_ty_name)) |si| { - const llvm_ty = si.llvm_type orelse self.ptrType(); - const loaded = c.LLVMBuildLoad2(self.builder, llvm_ty, thunk_param, "self_arg"); - try call_args.append(self.allocator, loaded); - } else { - // Primitive type — use typeToLLVM - const real_ty = Type.fromName(concrete_ty_name) orelse Type{ .struct_type = concrete_ty_name }; - const llvm_ty = self.typeToLLVM(real_ty); - const loaded = c.LLVMBuildLoad2(self.builder, llvm_ty, thunk_param, "self_arg"); - try call_args.append(self.allocator, loaded); - } - } else { - try call_args.append(self.allocator, thunk_param); - } - } - const call_args_slice = try call_args.toOwnedSlice(self.allocator); - - // Get the impl function's type for the call - const impl_fn_type = c.LLVMGlobalGetValueType(impl_fn); - const call_result = c.LLVMBuildCall2(self.builder, impl_fn_type, impl_fn, if (call_args_slice.len > 0) call_args_slice.ptr else null, @intCast(call_args_slice.len), if (ret_ty == .void_type) "" else "result"); - - if (ret_ty == .void_type) { - _ = c.LLVMBuildRetVoid(self.builder); - } else { - // Check if return type is Self (erased to ptr) but impl returns concrete value - const is_self_return = if (method.return_type) |rt| - rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Self") - else - false; - if (is_self_return) { - // Self return: impl returns concrete type (e.g., %Point), thunk must return ptr. - // Heap-allocate space, store result, return pointer. - const result_ty = c.LLVMTypeOf(call_result); - const size = c.LLVMSizeOf(result_ty); - const malloc_fn = self.getOrDeclareMalloc(); - const malloc_fn_ty = c.LLVMGlobalGetValueType(malloc_fn); - var malloc_args = [_]c.LLVMValueRef{size}; - const mem = c.LLVMBuildCall2(self.builder, malloc_fn_ty, malloc_fn, &malloc_args, 1, "self_ret"); - _ = c.LLVMBuildStore(self.builder, call_result, mem); - _ = c.LLVMBuildRet(self.builder, mem); - } else { - _ = c.LLVMBuildRet(self.builder, call_result); - } - } - } else { - // Fallback: unreachable (shouldn't happen — impl methods should be registered) - _ = c.LLVMBuildUnreachable(self.builder); - } - - if (saved_pos != null) { - c.LLVMPositionBuilderAtEnd(self.builder, saved_pos); - } - - thunks[i] = thunk_fn; - } - - // Store thunks keyed by "Protocol\x00Type" - const thunk_key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ pd.name, ib.target_type }); - try self.protocol_thunks.put(thunk_key, thunks); - - // For non-inline (vtable pointer) protocols, generate a static vtable constant - if (!pd.is_inline) { - const vtable_name = try std.fmt.allocPrint(self.allocator, "{s}.__Vtable", .{pd.name}); - if (self.lookupStructInfo(vtable_name)) |vtable_info| { - const vtable_llvm_ty = vtable_info.llvm_type orelse return; - // Build vtable constant: struct { thunk1, thunk2, ... } - var vtable_vals = try self.allocator.alloc(c.LLVMValueRef, pd.methods.len); - for (thunks, 0..) |thunk, i| { - vtable_vals[i] = thunk; - } - const vtable_const = c.LLVMConstNamedStruct(vtable_llvm_ty, vtable_vals.ptr, @intCast(pd.methods.len)); - - // Create a global constant for the vtable - const global_name = try std.fmt.allocPrint(self.allocator, "__{s}_{s}_vtable", .{ ib.target_type, pd.name }); - const global_name_z = try self.allocator.dupeZ(u8, global_name); - const vtable_global = c.LLVMAddGlobal(self.module, vtable_llvm_ty, global_name_z.ptr); - c.LLVMSetInitializer(vtable_global, vtable_const); - c.LLVMSetGlobalConstant(vtable_global, 1); - c.LLVMSetLinkage(vtable_global, c.LLVMPrivateLinkage); - } - } - } - - /// Generate function bodies for impl block methods. - fn genImplMethodBodies(self: *CodeGen, ib: ast.ImplBlock) !void { - // Generic impl methods are instantiated on demand - if (ib.target_type_params.len > 0) return; - - const saved_ns = self.current_namespace; - self.current_namespace = ib.target_type; - defer self.current_namespace = saved_ns; - - for (ib.methods) |method_node| { - const fd = method_node.data.fn_decl; - if (fd.type_params.len > 0) continue; // generic methods instantiated on demand - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, fd.name }); - if (shouldDeferFnBody(fd)) { - try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ib.target_type, .source_file = self.current_source_file }); - } else { - try self.genFnBody(fd, qualified); - } - } - - // Generate bodies for synthesized default methods (not overridden in impl) - if (self.protocol_decls.get(ib.protocol_name)) |pd| { - for (pd.methods) |method| { - if (method.default_body == null) continue; - // Check if overridden - var overridden = false; - for (ib.methods) |m| { - if (std.mem.eql(u8, m.data.fn_decl.name, method.name)) { - overridden = true; - break; - } - } - if (overridden) continue; - - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ib.target_type, method.name }); - - const fd = try self.synthesizeDefaultMethod(ib.target_type, method); - try self.genFnBody(fd, qualified); - } - } - } - - /// Synthesize a FnDecl for a protocol default method on a concrete type. - /// Creates: method_name :: (self: *ConcreteType, params...) -> R { default_body } - fn synthesizeDefaultMethod(self: *CodeGen, target_type: []const u8, method: ast.ProtocolMethodDecl) !ast.FnDecl { - var params = std.ArrayList(ast.Param).empty; - - // self: *ConcreteType — pointer_type_expr wrapping type_expr - const base_type_node = try self.allocator.create(Node); - base_type_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = target_type } } }; - const ptr_type_node = try self.allocator.create(Node); - ptr_type_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = base_type_node } } }; - try params.append(self.allocator, .{ - .name = "self", - .name_span = .{ .start = 0, .end = 0 }, - .type_expr = ptr_type_node, - }); - - // Protocol method params - for (method.params, 0..) |param_type, pi| { - const pname = if (pi < method.param_names.len) method.param_names[pi] else "arg"; - try params.append(self.allocator, .{ - .name = pname, - .name_span = .{ .start = 0, .end = 0 }, - .type_expr = param_type, - }); - } - - return ast.FnDecl{ - .name = method.name, - .params = try params.toOwnedSlice(self.allocator), - .return_type = method.return_type, - .body = method.default_body.?, - }; - } - - /// Build a protocol value struct from a concrete type pointer. - /// For #inline protocols: { ctx = ptr, fn1 = thunk1, fn2 = thunk2, ... } - fn buildProtocolValue(self: *CodeGen, val: c.LLVMValueRef, src_ty: Type, pd: ast.ProtocolDecl) !c.LLVMValueRef { - // Determine the concrete type name from src_ty - const concrete_type = if (src_ty.isPointer()) - src_ty.pointer_type.pointee_name - else if (src_ty.isStruct()) - src_ty.struct_type - else if (src_ty.isManyPointer()) - src_ty.many_pointer_type.element_name - else - return val; // can't convert, fallback - - // Look up thunks for this (Protocol, Type) pair - const thunk_key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ pd.name, concrete_type }); - const thunks = self.protocol_thunks.get(thunk_key) orelse return val; - - if (pd.is_inline) { - // Build inline protocol struct: { ctx, fn1, fn2, ... } - const proto_info = self.lookupStructInfo(pd.name) orelse return val; - const llvm_struct_ty = proto_info.llvm_type orelse return val; - - // Start with undef struct - var result = c.LLVMGetUndef(llvm_struct_ty); - - // ctx = val (the pointer to concrete type) - // If src is a value type (not pointer), we need to take its address - const ctx_ptr = if (src_ty.isPointer() or src_ty.isManyPointer()) - val - else blk: { - // Store value to a temp alloca and use its address - const tmp = self.buildEntryBlockAlloca(c.LLVMTypeOf(val), "proto_tmp"); - _ = c.LLVMBuildStore(self.builder, val, tmp); - break :blk tmp; - }; - result = self.insertValue(result, ctx_ptr, 0, "proto_ctx"); - - // Insert thunk function pointers - for (thunks, 0..) |thunk, i| { - result = self.insertValue(result, thunk, @intCast(1 + i), "proto_fn"); - } - - return result; - } - - // Non-inline (vtable pointer): { ctx = ptr, __vtable = &__Type_Proto_vtable } - const proto_info = self.lookupStructInfo(pd.name) orelse return val; - const llvm_struct_ty = proto_info.llvm_type orelse return val; - - var result = c.LLVMGetUndef(llvm_struct_ty); - - // ctx = ptr (same logic as inline) - const ctx_ptr = if (src_ty.isPointer() or src_ty.isManyPointer()) - val - else blk: { - const tmp = self.buildEntryBlockAlloca(c.LLVMTypeOf(val), "proto_tmp"); - _ = c.LLVMBuildStore(self.builder, val, tmp); - break :blk tmp; - }; - result = self.insertValue(result, ctx_ptr, 0, "proto_ctx"); - - // __vtable = pointer to static vtable global - const vtable_global_name = try std.fmt.allocPrint(self.allocator, "__{s}_{s}_vtable", .{ concrete_type, pd.name }); - var vbuf: [256]u8 = undefined; - const vtable_global = c.LLVMGetNamedGlobal(self.module, self.nameToCStr(vtable_global_name, &vbuf)); - if (vtable_global != null) { - result = self.insertValue(result, vtable_global, 1, "proto_vtable"); - } - - return result; - } - - /// Register a tagged enum name with a placeholder entry. - /// Called during registerTypes phase. - fn registerTaggedEnumName(self: *CodeGen, ud: ast.EnumDecl) !void { - try self.type_registry.put(ud.name, .{ .tagged_enum = .{ - .variant_names = &.{}, - .variant_types = &.{}, - .llvm_type = null, - .max_payload_size = 0, - .payload_field_index = 0, - } }); - - for (ud.variant_types, 0..) |vt_opt, i| { - if (vt_opt) |vt| { - try self.hoistInlineTypeDecl(ud.name, ud.variant_names[i], vt); - } - } - } - - /// Resolve tagged enum variant types and compute LLVM layout. - /// Called during resolveFields phase after all type names are registered. - fn resolveTaggedEnumFields(self: *CodeGen, ud: ast.EnumDecl) !void { - const layout_info = try self.resolveEnumLayout(ud); - - if (layout_info) |layout| { - try self.enum_backing_types.put(ud.name, layout.tag_llvm_type); - - 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); - } - } - - try self.type_registry.put(ud.name, .{ .tagged_enum = .{ - .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, - } }); - } else { - 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); - - try self.type_registry.put(ud.name, .{ .tagged_enum = .{ - .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.getAnyTypeId(ud.name, .{ .union_type = ud.name }); - - 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 = self.resolveTypeFromName(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, - }; - } - - /// Register a union type name with a placeholder entry. - /// Called during registerTypes phase. - fn registerUnionName(self: *CodeGen, ud: ast.UnionDecl) !void { - try self.type_registry.put(ud.name, .{ .union_info = .{ - .field_names = &.{}, - .field_types = &.{}, - .llvm_type = null, - .total_size = 0, - .promoted_fields = std.StringHashMap(PromotedField).init(self.allocator), - } }); - - for (ud.field_types, 0..) |ft, i| { - try self.hoistInlineTypeDecl(ud.name, ud.field_names[i], ft); - } - } - - /// Resolve union field types and compute LLVM layout. - /// Called during resolveFields phase after all type names are registered. - fn resolveUnionFields(self: *CodeGen, ud: ast.UnionDecl) !void { - 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; - } - - 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()) { - 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 }); - } - - fn genTaggedEnumLiteral(self: *CodeGen, variant_name: []const u8, payload_node: ?*Node, 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, variant_name)) { - variant_idx = @intCast(i); - break; - } - } - const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ variant_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 (payload_node) |pnode| { - const variant_ty = info.variant_types[idx]; - if (variant_ty != .void_type) { - const payload_val = try self.genExprAsType(pnode, variant_ty); - self.storeStructField(info.llvm_type, alloca, info.payload_field_index, payload_val); - } - } - - return c.LLVMBuildLoad2(self.builder, info.llvm_type, alloca, "union_val"); - } - - 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); - } - } - - // Init block: T.{ ... } { self.method(); } - if (sl.init_block) |ib| { - if (ib.data == .block) { - try self.pushScope(); - const self_ty = Type{ .pointer_type = .{ .pointee_name = sname } }; - // self is a *T — store the struct alloca address into a ptr-sized alloca - const self_alloca = self.buildEntryBlockAlloca(self.ptrType(), "self"); - _ = c.LLVMBuildStore(self.builder, alloca, self_alloca); - try self.registerVariable("self", self_alloca, self_ty); - for (ib.data.block.stmts) |stmt| { - _ = try self.genStmt(stmt); - } - try self.popScope(); - } - } - - return alloca; - } - - /// Resolve a field name or numeric index to a tuple field index. - fn resolveTupleFieldIndex(info: Type.TupleTypeInfo, field: []const u8) ?usize { - // Try numeric index first: "0", "1", etc. - if (std.fmt.parseInt(usize, field, 10)) |idx| { - if (idx < info.field_types.len) return idx; - } else |_| {} - // Try named lookup - if (info.field_names) |names| { - for (names, 0..) |name, i| { - if (std.mem.eql(u8, name, field)) return i; - } - } - return null; - } - - fn genTupleLiteral(self: *CodeGen, tl: ast.TupleLiteral) anyerror!c.LLVMValueRef { - const n = tl.elements.len; - // Infer types for each element - const field_types = try self.allocator.alloc(Type, n); - const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n); - for (tl.elements, 0..) |elem, i| { - field_types[i] = self.inferType(elem.value); - field_llvm_types[i] = self.typeToLLVM(field_types[i]); - } - - // Build anonymous LLVM struct type - const llvm_type = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n), 0); - - // Alloca and store each element - const alloca = self.buildEntryBlockAlloca(llvm_type, "tuple"); - for (tl.elements, 0..) |elem, i| { - const val = try self.genExprAsType(elem.value, field_types[i]); - self.storeStructField(llvm_type, alloca, @intCast(i), val); - } - - // Store the tuple type info for later field access - const field_names = if (tl.elements[0].name != null) blk: { - const names = try self.allocator.alloc([]const u8, n); - for (tl.elements, 0..) |elem, i| { - names[i] = elem.name orelse ""; - } - break :blk @as(?[]const []const u8, names); - } else null; - - // Register this alloca as having tuple type - const tuple_ty = Type{ .tuple_type = .{ - .field_names = field_names, - .field_types = field_types, - } }; - try self.tuple_alloca_types.put(@intFromPtr(alloca), tuple_ty); - - 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 elem_node = al.elements[i]; - const val = try self.genExprAsType(elem_node, elem_sx_ty); - const gep = self.gepArrayElement(llvm_arr_ty, alloca, self.constInt32(@intCast(i)), "arr_elem"); - // Array literals return allocas via genArrayLiteral — load value before storing - if (elem_node.data == .array_literal) { - const elem_llvm_ty = self.typeToLLVM(elem_sx_ty); - const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, val, "agg_load"); - _ = c.LLVMBuildStore(self.builder, loaded, gep); - } else { - _ = 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; - var val = try self.genExpr(inner); - const src_ty = self.inferType(inner); - // genExpr on struct literals returns an alloca (ptr), not a loaded value. - // Load it so convertValue/buildProtocolValue sees the actual struct value. - if (inner.data == .struct_literal and src_ty.isStruct()) { - const sname = self.resolveAlias(src_ty.struct_type); - if (self.lookupStructInfo(sname)) |si| { - val = c.LLVMBuildLoad2(self.builder, si.llvm_type.?, val, "xx_struct_load"); - } - } - 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"); - } - - // Optional target type: wrap value or produce null - if (target_ty.isOptional()) { - // null literal → null optional - if (node.data == .null_literal) { - return self.makeNullOptional(target_ty); - } - - // if_expr in optional context: propagate optional type to each branch - // so that `null` → makeNullOptional and values → wrapOptional individually - if (node.data == .if_expr) { - const ie = node.data.if_expr; - if (ie.binding_name == null and ie.else_branch != null) { - const cond_val = self.valueToBool(try self.genExpr(ie.condition)); - - var then_bb = self.appendBB("opt_then"); - var else_bb = self.appendBB("opt_else"); - const merge_bb = self.appendBB("opt_merge"); - - self.condBr(cond_val, then_bb, else_bb); - - self.positionAt(then_bb); - const then_val = try self.genExprAsType(ie.then_branch, target_ty); - then_bb = self.getCurrentBlock(); - self.br(merge_bb); - - self.positionAt(else_bb); - const else_val = try self.genExprAsType(ie.else_branch.?, target_ty); - else_bb = self.getCurrentBlock(); - self.br(merge_bb); - - self.positionAt(merge_bb); - - const llvm_ty = self.typeToLLVM(target_ty); - const phi = c.LLVMBuildPhi(self.builder, llvm_ty, "opt_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; - } - } - - // If source expression already produces the same optional type, pass through - const src_ty = self.inferType(node); - if (src_ty.eql(target_ty)) { - return try self.genExpr(node); - } - // If source is a different optional, generate as-is (e.g. ?s32 → ?s64 widening) - if (src_ty.isOptional()) { - return try self.genExpr(node); - } - // Expression producing a value — generate it as the inner type, then wrap - const child_name = target_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse - return self.emitErrorFmt("unknown optional inner type '{s}'", .{child_name}); - const val = try self.genExprAsType(node, child_ty); - return self.wrapOptional(val, target_ty); - } - - // Match/if expression with enum/union target: propagate type context into arms - // so enum literals like .red can resolve their type from the assignment target - if ((node.data == .match_expr or node.data == .if_expr) and - (target_ty.isEnum() or target_ty.isUnion())) - { - const saved = self.current_return_type; - self.current_return_type = target_ty; - const val = try self.genExpr(node); - self.current_return_type = saved; - return val; - } - - // 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 or node.data.binary_op.op == .bit_xor) 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 switch (binop.op) { - .bit_or => c.LLVMBuildOr(b, lhs, rhs, "bortmp"), - .bit_and => c.LLVMBuildAnd(b, lhs, rhs, "bandtmp"), - .bit_xor => c.LLVMBuildXor(b, lhs, rhs, "bxortmp"), - else => unreachable, - }; - } - - // Enum/union literal assigned to union type: construct tagged enum - if (node.data == .enum_literal and target_ty.isUnion()) { - return self.genTaggedEnumLiteral(node.data.enum_literal.name, null, target_ty.union_type); - } - - // Call with enum_literal callee: .variant(payload) or .method(args) with known target type - if (node.data == .call and node.data.call.callee.data == .enum_literal) { - const call_node = node.data.call; - const el_name = call_node.callee.data.enum_literal.name; - - if (target_ty.isUnion()) { - const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null; - return self.genTaggedEnumLiteral(el_name, payload_node, target_ty.union_type); - } - - if (target_ty.isStruct()) { - const struct_name = self.resolveAlias(target_ty.struct_type); - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ struct_name, el_name }); - return self.genCallByName(qualified, call_node); - } - } - - // 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 protocol type: auto type erasure - if (node.data == .struct_literal and target_ty.isStruct()) { - if (self.protocol_decls.get(target_ty.struct_type)) |pd| { - const sl = node.data.struct_literal; - const concrete_name = sl.struct_name orelse if (sl.type_expr) |te| blk: { - const ty = self.resolveType(te); - if (ty.isStruct()) break :blk ty.struct_type; - break :blk null; - } else null; - if (concrete_name) |cn| { - const key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ pd.name, cn }); - if (self.impl_blocks.contains(key)) { - const alloca = try self.genStructLiteral(sl, cn); - const sname = self.resolveAlias(cn); - if (self.lookupStructInfo(sname)) |si| { - const loaded = c.LLVMBuildLoad2(self.builder, si.llvm_type.?, alloca, "auto_erase_load"); - return self.buildProtocolValue(loaded, Type{ .struct_type = cn }, pd) catch return loaded; - } - } - } - } - } - - // 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); - } - - // closure() with inferred params → provide type context from target - if (target_ty.isClosureType() and node.data == .call) { - const call_d = node.data.call; - if (call_d.callee.data == .identifier and - std.mem.eql(u8, call_d.callee.data.identifier.name, "closure")) - { - if (call_d.args.len == 1 and call_d.args[0].data == .lambda) { - const lam = call_d.args[0].data.lambda; - const has_inferred = for (lam.params) |p| { - if (p.type_expr.data == .inferred_type) break true; - } else false; - if (has_inferred) { - const saved_cet = self.closure_expected_type; - self.closure_expected_type = target_ty.closure_type; - defer self.closure_expected_type = saved_cet; - return try self.genExpr(node); - } - } - } - } - - // 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"); - } - } - - // Auto-promotion: bare function → Closure (static thunk + null env) - if (target_ty.isClosureType()) { - if (src_ty.isFunctionType() or self.isFunctionName(node)) { - return self.promoteToClosureThunk(node, target_ty.closure_type); - } - } - - // 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; - } - } - // Field access / index: take address of the lvalue directly (no copy) - if (node.data == .field_access or node.data == .index_expr) { - return try self.genAddressOf(node); - } - // Other non-identifier expressions: copy into temp alloca - if (node.data != .identifier) { - const val = try self.genExpr(node); - const llvm_ty = c.LLVMTypeOf(val); - const tmp = self.buildEntryBlockAlloca(llvm_ty, "implicit_addr"); - _ = c.LLVMBuildStore(self.builder, val, tmp); - return tmp; - } - } - } - - 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"); - } - } - } - - // Auto type erasure: concrete type → protocol (implicit xx) - if (target_ty.isStruct()) { - if (self.protocol_decls.get(target_ty.struct_type)) |pd| { - const concrete_name = if (src_ty.isStruct()) - src_ty.struct_type - else if (src_ty.isPointer()) - src_ty.pointer_type.pointee_name - else if (src_ty.isManyPointer()) - src_ty.many_pointer_type.element_name - else - null; - if (concrete_name) |cn| { - const key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ pd.name, cn }); - if (self.impl_blocks.contains(key)) { - return self.buildProtocolValue(val, src_ty, pd) catch return val; - } - } - } - } - - // 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; - - // Protocol conversion: concrete type → protocol value via xx - if (target_ty.isStruct()) { - if (self.protocol_decls.get(target_ty.struct_type)) |pd| { - return self.buildProtocolValue(val, src_ty, pd) catch 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; - } - - // function_type → function_type: both are opaque pointers at LLVM level, no-op - // Enables xx cast between different function pointer signatures - if (src_ty.isFunctionType() and target_ty.isFunctionType()) { - 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 = self.resolveTypeFromName(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 = self.resolveTypeFromName(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 = self.resolveTypeFromName(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 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 genMemset(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { - if (args.len != 3) return self.emitError("memset expects 3 arguments: memset(dst, val, size)"); - const builtins = try self.requireBuiltins(); - const dst = try self.genExpr(args[0]); - const val = try self.genExpr(args[1]); - const size_val = try self.genExpr(args[2]); - const val_i32 = self.trunc(val, self.i32Type(), "memset_val"); - const fn_ty = c.LLVMGlobalGetValueType(builtins.memset_fn); - var call_args = [_]c.LLVMValueRef{ dst, val_i32, size_val }; - _ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.memset_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}); - } - - /// Optional chaining: expr?.field — short-circuit to null if expr is null - fn genOptionalChain(self: *CodeGen, fa: ast.FieldAccess) !c.LLVMValueRef { - const opt_ty = self.inferType(fa.object); - if (!opt_ty.isOptional()) { - return self.emitError("'?.' used on non-optional type"); - } - - const opt_val = try self.genExpr(fa.object); - const has_val = self.optionalHasValue(opt_val, opt_ty); - - const some_bb = self.appendBB("chain_some"); - const none_bb = self.appendBB("chain_none"); - const merge_bb = self.appendBB("chain_merge"); - _ = c.LLVMBuildCondBr(self.builder, has_val, some_bb, none_bb); - - // Some: unwrap, access field, re-wrap as ?FieldType - self.positionAt(some_bb); - const payload = self.optionalPayload(opt_val, opt_ty); - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse - return self.emitErrorFmt("unknown optional inner type '{s}'", .{child_name}); - - // Create a synthetic non-optional field_access to reuse genFieldAccess - const inner_fa = ast.FieldAccess{ .object = fa.object, .field = fa.field, .is_optional = false }; - // We need the field type to construct ?FieldType - const field_ty = self.inferFieldType(child_ty, fa.field) orelse - return self.emitErrorFmt("type '{s}' has no field '{s}'", .{ child_name, fa.field }); - - // Generate the field access on the unwrapped payload - const field_val = try self.genFieldAccessOnValue(payload, child_ty, inner_fa.field); - const result_opt_ty = Type{ .optional_type = .{ .child_name = try field_ty.displayName(self.allocator) } }; - const some_result = self.wrapOptional(field_val, result_opt_ty); - const some_out_bb = self.getCurrentBlock(); - self.br(merge_bb); - - // None: produce null optional - self.positionAt(none_bb); - const none_result = self.makeNullOptional(result_opt_ty); - const none_out_bb = self.getCurrentBlock(); - self.br(merge_bb); - - // Merge - self.positionAt(merge_bb); - var phi_vals = std.ArrayList(c.LLVMValueRef).empty; - var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; - try phi_vals.append(self.allocator, some_result); - try phi_bbs.append(self.allocator, some_out_bb); - try phi_vals.append(self.allocator, none_result); - try phi_bbs.append(self.allocator, none_out_bb); - const result_llvm_ty = self.typeToLLVM(result_opt_ty); - return try self.buildPhiNode(&phi_vals, &phi_bbs, result_llvm_ty, "chain_result"); - } - - /// Infer the type of a field on a given type - fn inferFieldType(self: *CodeGen, ty: Type, field: []const u8) ?Type { - if (ty.isStruct()) { - const info = self.lookupStructInfo(ty.struct_type) orelse return null; - for (info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, field)) { - return info.field_types[i]; - } - } - } - // string/slice .ptr/.len - if (ty == .string_type or ty.isSlice()) { - if (std.mem.eql(u8, field, "len")) return .{ .signed = 64 }; - if (std.mem.eql(u8, field, "ptr")) return .{ .pointer_type = .{ .pointee_name = "u8" } }; - } - return null; - } - - /// Generate field access on a raw value (not from the AST) - fn genFieldAccessOnValue(self: *CodeGen, val: c.LLVMValueRef, val_ty: Type, field: []const u8) !c.LLVMValueRef { - if (val_ty.isStruct()) { - const sname = val_ty.struct_type; - const info = self.lookupStructInfo(sname) orelse - return self.emitErrorFmt("unknown struct '{s}'", .{sname}); - for (info.field_names, 0..) |fname, fi| { - if (std.mem.eql(u8, fname, field)) { - // val is a loaded struct value — store to temp alloca, GEP, load field - const alloca = self.buildEntryBlockAlloca(info.llvm_type, "chain_tmp"); - _ = c.LLVMBuildStore(self.builder, val, alloca); - const field_llvm_ty = self.typeToLLVM(info.field_types[fi]); - return self.loadStructField(info.llvm_type, alloca, @intCast(fi), field_llvm_ty); - } - } - return self.emitErrorFmt("struct '{s}' has no field '{s}'", .{ sname, field }); - } - // string/slice .ptr/.len - if (val_ty == .string_type or val_ty.isSlice()) { - const str_ty = self.getStringStructType(); - if (std.mem.eql(u8, field, "ptr")) { - return c.LLVMBuildExtractValue(self.builder, val, 0, "chain_ptr"); - } - if (std.mem.eql(u8, field, "len")) { - return c.LLVMBuildExtractValue(self.builder, val, 1, "chain_len"); - } - _ = str_ty; - } - return self.emitErrorFmt("cannot access field '{s}' via optional chaining", .{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.getNamedOrGlobal(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"); - } - if (pointee_ty.isUnion()) { - const uname = pointee_ty.union_type; - 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, loaded_ptr, "punion_field"); - } - if (info.promoted_fields.get(fa.field)) |pf| { - const sinfo = try self.getStructInfo(pf.struct_name); - return self.loadStructField(sinfo.llvm_type, loaded_ptr, @intCast(pf.field_index), self.typeToLLVM(pf.field_type)); - } - return self.emitErrorFmt("no field '{s}' in union '{s}'", .{ fa.field, uname }); - } - } - 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.isTuple()) { - const ti = entry.ty.tuple_type; - const idx = resolveTupleFieldIndex(ti, fa.field) orelse - return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field}); - const llvm_ty = self.typeToLLVM(entry.ty); - const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]); - return self.loadStructField(llvm_ty, entry.ptr, @intCast(idx), field_llvm_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.isClosureType()) { - const closure_val = c.LLVMBuildLoad2(self.builder, self.getClosureStructType(), entry.ptr, "closure_load"); - return self.extractClosureField(closure_val, fa.field); - } - 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}); - } - } - // Namespace constant: TypeName.CONSTANT - const ns_name = fa.object.data.identifier.name; - if (self.namespaces.contains(ns_name) or self.type_registry.contains(ns_name)) { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field }); - if (self.comptime_globals.getPtr(qualified)) |ct| { - if (!ct.is_resolved) try self.resolveComptimeGlobal(ct); - return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ct.ty), ct.global, "struct_const"); - } - } - } - // Non-identifier object: evaluate expression and check type - const obj_val = try self.genExpr(fa.object); - const obj_ty = self.inferType(fa.object); - // Pointer auto-deref: expr.field where expr is *T → load through pointer - if (obj_ty.isPointer()) { - const pointee_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse - return self.emitError("unknown pointee type for auto-deref"); - 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, obj_val, @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(), obj_val, "pslice_load"); - return self.extractFatPtrField(slice_val, fa.field, "*slice"); - } - return self.emitErrorFmt("no field '{s}' on pointer", .{fa.field}); - } - if (obj_ty.isVector()) { - return self.genVectorExtract(obj_val, fa.field); - } - if (obj_ty == .string_type) { - return self.extractFatPtrField(obj_val, fa.field, "string"); - } - if (obj_ty.isSlice()) { - return self.extractFatPtrField(obj_val, fa.field, "slice"); - } - if (obj_ty.isClosureType()) { - return self.extractClosureField(obj_val, fa.field); - } - if (obj_ty.isStruct()) { - const sname = obj_ty.struct_type; - const info = try self.getStructInfo(sname); - const idx = try self.findFieldIndex(info.field_names, fa.field, sname); - // Store the struct value to a temp alloca, then GEP to load the field - const tmp = self.buildEntryBlockAlloca(info.llvm_type, "tmp_struct"); - _ = c.LLVMBuildStore(self.builder, obj_val, tmp); - return self.loadStructField(info.llvm_type, tmp, @intCast(idx), self.typeToLLVM(info.field_types[idx])); - } - if (obj_ty.isUnion()) { - if (self.lookupTaggedEnumInfo(obj_ty.union_type)) |info| { - for (info.variant_names, 0..) |vn, i| { - if (std.mem.eql(u8, vn, fa.field)) { - const variant_ty = info.variant_types[i]; - if (variant_ty == .void_type) return self.emitErrorFmt("cannot access payload of void variant '{s}'", .{fa.field}); - const tmp = self.buildEntryBlockAlloca(info.llvm_type, "tmp_enum"); - _ = c.LLVMBuildStore(self.builder, obj_val, tmp); - return self.loadStructField(info.llvm_type, tmp, info.payload_field_index, self.typeToLLVM(variant_ty)); - } - } - } - } - if (obj_ty.isTuple()) { - const ti = obj_ty.tuple_type; - const idx = resolveTupleFieldIndex(ti, fa.field) orelse - return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field}); - const llvm_ty = self.typeToLLVM(obj_ty); - const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]); - // If obj is a tuple literal, genExpr returned an alloca pointer directly - if (fa.object.data == .tuple_literal) { - return self.loadStructField(llvm_ty, obj_val, @intCast(idx), field_llvm_ty); - } - // Otherwise obj_val is a loaded struct value — store into tmp and GEP - const tmp = self.buildEntryBlockAlloca(llvm_ty, "tmp_tuple"); - _ = c.LLVMBuildStore(self.builder, obj_val, tmp); - return self.loadStructField(llvm_ty, tmp, @intCast(idx), field_llvm_ty); - } - 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"); - } - // General fallback: store value to temp alloca and GEP into it - { - const arr_val = try self.genExpr(ie.object); - const arr_llvm_ty = self.typeToLLVM(obj_ty); - const tmp = self.buildEntryBlockAlloca(arr_llvm_ty, "arr_tmp"); - _ = c.LLVMBuildStore(self.builder, arr_val, tmp); - const idx = try self.genExpr(ie.index); - const gep = self.gepArrayElement(arr_llvm_ty, tmp, idx, "gen_arridx"); - return self.loadTyped(elem_ty, gep, "gen_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(self.resolveTypeFromName(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"), - .bit_xor => c.LLVMBuildXor(b, lhs, rhs, "bxortmp"), - .shl => c.LLVMBuildShl(b, lhs, rhs, "shltmp"), - .shr => if (is_unsigned) c.LLVMBuildLShr(b, lhs, rhs, "shrtmp") else c.LLVMBuildAShr(b, lhs, rhs, "shrtmp"), - .and_op, .or_op, .in_op => unreachable, - }; - } - - fn genStringComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef) !c.LLVMValueRef { - const b = self.builder; - const i64_ty = self.i64Type(); - const i32_ty = self.i32Type(); - const i1_ty = self.i1Type(); - const ptr_ty = self.ptrType(); - - // Extract ptr and len from both fat pointers - const lhs_ptr = self.extractValue(lhs, 0, "lptr"); - const lhs_len = self.extractValue(lhs, 1, "llen"); - const rhs_ptr = self.extractValue(rhs, 0, "rptr"); - const rhs_len = self.extractValue(rhs, 1, "rlen"); - - // Compare lengths first - const len_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, lhs_len, rhs_len, "len_eq"); - - // Set up basic blocks for conditional memcmp - const cur_bb = self.getCurrentBlock(); - const memcmp_bb = self.appendBB("str.memcmp"); - const merge_bb = self.appendBB("str.merge"); - - _ = c.LLVMBuildCondBr(b, len_eq, memcmp_bb, merge_bb); - - // memcmp block: lengths match, compare content - c.LLVMPositionBuilderAtEnd(b, memcmp_bb); - const memcmp_fn = c.LLVMGetNamedFunction(self.module, "memcmp") orelse blk: { - var params = [_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty }; - const fn_type = c.LLVMFunctionType(i32_ty, ¶ms, 3, 0); - break :blk c.LLVMAddFunction(self.module, "memcmp", fn_type); - }; - var args = [_]c.LLVMValueRef{ lhs_ptr, rhs_ptr, lhs_len }; - const cmp_result = c.LLVMBuildCall2(b, c.LLVMGlobalGetValueType(memcmp_fn), memcmp_fn, &args, 3, "memcmp"); - const content_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, cmp_result, c.LLVMConstInt(i32_ty, 0, 0), "content_eq"); - _ = c.LLVMBuildBr(b, merge_bb); - - // Merge: phi(len_mismatch=false, memcmp_result) - c.LLVMPositionBuilderAtEnd(b, merge_bb); - const phi = c.LLVMBuildPhi(b, i1_ty, "str_eq"); - const false_val = c.LLVMConstInt(i1_ty, 0, 0); - var phi_vals = [_]c.LLVMValueRef{ false_val, content_eq }; - var phi_bbs = [_]c.LLVMBasicBlockRef{ cur_bb, memcmp_bb }; - c.LLVMAddIncoming(phi, &phi_vals, &phi_bbs, 2); - - if (op == .neq) { - return c.LLVMBuildNot(b, phi, "str_neq"); - } - return phi; - } - - fn genTupleComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef { - const lhs_info = lhs_ty.tuple_type; - const rhs_info = rhs_ty.tuple_type; - const n = lhs_info.field_types.len; - if (n != rhs_info.field_types.len) return self.emitError("tuple comparison requires same field count"); - - const lhs_val = try self.genExpr(lhs_node); - const rhs_val = try self.genExpr(rhs_node); - const lhs_llvm_ty = self.typeToLLVM(lhs_ty); - const rhs_llvm_ty = self.typeToLLVM(rhs_ty); - const i1_ty = self.i1Type(); - - // Equality: AND-reduce field-wise == comparisons - if (op == .eq or op == .neq) { - var result = c.LLVMConstInt(i1_ty, 1, 0); // start with true - for (0..n) |i| { - const field_ty = lhs_info.field_types[i]; - const field_llvm_ty = self.typeToLLVM(field_ty); - const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty); - const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty); - const cmp = self.genBinaryOp(.eq, lf, rf, field_ty); - result = c.LLVMBuildAnd(self.builder, result, cmp, "tuple_eq_and"); - } - if (op == .neq) return c.LLVMBuildNot(self.builder, result, "tuple_neq"); - return result; - } - - // Lexicographic comparison: chain of basic blocks - // For < : at each field, if lhs.i < rhs.i → true, if lhs.i > rhs.i → false, else continue - // For > : swap the sense - // For <= : same as < but tie → true - // For >= : same as > but tie → true - const is_less = (op == .lt or op == .lte); - const tie_result: u64 = if (op == .lte or op == .gte) 1 else 0; - - const merge_bb = self.appendBB("tup.cmp.merge"); - const phi_count = 2 * n + 1; - const phi_vals = try self.allocator.alloc(c.LLVMValueRef, phi_count); - const phi_bbs = try self.allocator.alloc(c.LLVMBasicBlockRef, phi_count); - var phi_idx: usize = 0; - - for (0..n) |i| { - const field_ty = lhs_info.field_types[i]; - const field_llvm_ty = self.typeToLLVM(field_ty); - const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty); - const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty); - - // Check if lhs.i < rhs.i (or > if !is_less) - const less_op: ast.BinaryOp.Op = if (is_less) .lt else .gt; - const cmp_less = self.genBinaryOp(less_op, lf, rf, field_ty); - phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 1, 0); // true - phi_bbs[phi_idx] = self.getCurrentBlock(); - phi_idx += 1; - const next_bb = self.appendBB("tup.cmp.next"); - _ = c.LLVMBuildCondBr(self.builder, cmp_less, merge_bb, next_bb); - - c.LLVMPositionBuilderAtEnd(self.builder, next_bb); - - // Check if lhs.i > rhs.i (or < if !is_less) - const greater_op: ast.BinaryOp.Op = if (is_less) .gt else .lt; - const cmp_greater = self.genBinaryOp(greater_op, lf, rf, field_ty); - phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 0, 0); // false - phi_bbs[phi_idx] = self.getCurrentBlock(); - phi_idx += 1; - const eq_bb = self.appendBB("tup.cmp.eq"); - _ = c.LLVMBuildCondBr(self.builder, cmp_greater, merge_bb, eq_bb); - - c.LLVMPositionBuilderAtEnd(self.builder, eq_bb); - } - - // All fields equal — tie - phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, tie_result, 0); - phi_bbs[phi_idx] = self.getCurrentBlock(); - phi_idx += 1; - _ = c.LLVMBuildBr(self.builder, merge_bb); - - c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); - const phi = c.LLVMBuildPhi(self.builder, i1_ty, "tup_cmp"); - c.LLVMAddIncoming(phi, phi_vals.ptr, @ptrCast(phi_bbs.ptr), @intCast(phi_idx)); - return phi; - } - - fn genTupleConcat(self: *CodeGen, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef { - const lhs_info = lhs_ty.tuple_type; - const rhs_info = rhs_ty.tuple_type; - const n_lhs = lhs_info.field_types.len; - const n_rhs = rhs_info.field_types.len; - const n_total = n_lhs + n_rhs; - - // Build new tuple type - const field_types = try self.allocator.alloc(Type, n_total); - const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total); - for (0..n_lhs) |i| { - field_types[i] = lhs_info.field_types[i]; - field_llvm_types[i] = self.typeToLLVM(field_types[i]); - } - for (0..n_rhs) |i| { - field_types[n_lhs + i] = rhs_info.field_types[i]; - field_llvm_types[n_lhs + i] = self.typeToLLVM(field_types[n_lhs + i]); - } - - const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0); - const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_cat"); - - // Copy fields from lhs - const lhs_val = try self.genExpr(lhs_node); - const lhs_llvm_ty = self.typeToLLVM(lhs_ty); - for (0..n_lhs) |i| { - const fv = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_types[i]); - self.storeStructField(result_llvm_ty, alloca, @intCast(i), fv); - } - - // Copy fields from rhs - const rhs_val = try self.genExpr(rhs_node); - const rhs_llvm_ty = self.typeToLLVM(rhs_ty); - for (0..n_rhs) |i| { - const fv = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_types[n_lhs + i]); - self.storeStructField(result_llvm_ty, alloca, @intCast(n_lhs + i), fv); - } - - // Register tuple type - const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } }; - try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty); - return alloca; - } - - fn genTupleRepeat(self: *CodeGen, tuple_node: *ast.Node, count_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef { - // Count must be a comptime int literal - const count: usize = switch (count_node.data) { - .int_literal => |il| @intCast(il.value), - else => return self.emitError("tuple repetition count must be a compile-time integer literal"), - }; - if (count == 0) return self.emitError("tuple repetition count must be positive"); - - const info = tuple_ty.tuple_type; - const n_fields = info.field_types.len; - const n_total = n_fields * count; - - // Build new tuple type - const field_types = try self.allocator.alloc(Type, n_total); - const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total); - for (0..count) |r| { - for (0..n_fields) |f| { - field_types[r * n_fields + f] = info.field_types[f]; - field_llvm_types[r * n_fields + f] = self.typeToLLVM(info.field_types[f]); - } - } - - const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0); - const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_rep"); - - // Generate tuple value once - const tuple_val = try self.genExpr(tuple_node); - const tuple_llvm_ty = self.typeToLLVM(tuple_ty); - - // Copy fields for each repetition - for (0..count) |r| { - for (0..n_fields) |f| { - const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(f), field_llvm_types[r * n_fields + f]); - self.storeStructField(result_llvm_ty, alloca, @intCast(r * n_fields + f), fv); - } - } - - const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } }; - try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty); - return alloca; - } - - fn genTupleMembership(self: *CodeGen, value_node: *ast.Node, tuple_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef { - const info = tuple_ty.tuple_type; - const n = info.field_types.len; - if (n == 0) return c.LLVMConstInt(self.i1Type(), 0, 0); - - const value = try self.genExpr(value_node); - const tuple_val = try self.genExpr(tuple_node); - const tuple_llvm_ty = self.typeToLLVM(tuple_ty); - const value_ty = self.inferType(value_node); - const i1_ty = self.i1Type(); - - // OR-reduce: (field0 == val) OR (field1 == val) OR ... - var result = c.LLVMConstInt(i1_ty, 0, 0); // start with false - for (0..n) |i| { - const field_ty = info.field_types[i]; - const field_llvm_ty = self.typeToLLVM(field_ty); - const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(i), field_llvm_ty); - const common_ty = Type.widen(value_ty, field_ty); - const cmp = self.genBinaryOp(.eq, value, fv, common_ty); - result = c.LLVMBuildOr(self.builder, result, cmp, "tuple_in_or"); - } - return result; - } - - 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 { - // Dot-shorthand call: .variant(payload) or .method(args) with type inferred from context - if (call_node.callee.data == .enum_literal) { - const el_name = call_node.callee.data.enum_literal.name; - - if (self.current_return_type.isUnion()) { - const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null; - return self.genTaggedEnumLiteral(el_name, payload_node, self.current_return_type.union_type); - } - - if (self.current_return_type.isStruct()) { - const struct_name = self.resolveAlias(self.current_return_type.struct_type); - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ struct_name, el_name }); - return self.genCallByName(qualified, call_node); - } - - return self.emitErrorFmt("cannot infer type for '.{s}(...)' call", .{el_name}); - } - - 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(fa.field, 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); - } - } - - // Struct field function pointer / closure call: obj.field(args) - // Checked before UFCS so that struct fields shadow free functions of the same name. - // SKIP for protocol types — protocol fn-ptr fields are dispatched via wrapper methods. - { - var obj_ty = self.inferType(fa.object); - if (obj_ty.isPointer()) { - obj_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse obj_ty; - } - if (obj_ty.isStruct() and !self.protocol_decls.contains(obj_ty.struct_type)) { - if (self.lookupStructInfo(obj_ty.struct_type)) |info| { - if (self.findNameIndex(info.field_names, fa.field)) |idx| { - const field_ty = info.field_types[idx]; - if (field_ty.isFunctionType()) { - const fn_ptr = try self.genFieldAccess(fa); - return self.genIndirectCallFromPtr(fn_ptr, field_ty.function_type, call_node); - } - if (field_ty.isClosureType()) { - const closure_val = try self.genFieldAccess(fa); - return self.genClosureCallFromValue(closure_val, field_ty.closure_type, call_node); - } - } - } - } - // Direct closure variable field call: c(args) where c is a local Closure variable - if (obj_ty.isClosureType()) { - // This handles the case where obj is a closure and fa.field is being called as UFCS, - // but actually we want obj.fn_ptr/env access + call. This path is for - // closures stored in struct fields — the direct variable call goes through genCallByName. - } - } - - // Struct method: obj.method(args) → StructName.method(obj, args) - { - var obj_ty2 = self.inferType(fa.object); - if (obj_ty2.isPointer()) { - obj_ty2 = self.resolveTypeFromName(obj_ty2.pointer_type.pointee_name) orelse obj_ty2; - } - if (obj_ty2.isStruct()) { - const struct_name = obj_ty2.struct_type; - // Try base struct name and template name for generic structs - const template_name = if (self.lookupStructInfo(struct_name)) |si| si.template_name orelse struct_name else struct_name; - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ template_name, fa.field }); - const qualified_z = try self.allocator.dupeZ(u8, qualified); - if (self.generic_templates.contains(qualified) or - c.LLVMGetNamedFunction(self.module, qualified_z.ptr) != null) - { - var method_args = try self.allocator.alloc(*Node, call_node.args.len + 1); - method_args[0] = fa.object; - for (call_node.args, 0..) |arg, i| { - method_args[i + 1] = arg; - } - return self.genCallByName(qualified, .{ - .callee = call_node.callee, - .args = method_args, - }); - } - } - // Non-struct impl method: obj.method(args) for primitive/other types with impl blocks - // e.g., s32.eq(other) via `impl Eq for s32` - if (!obj_ty2.isStruct()) { - const type_name = obj_ty2.toName(); - if (type_name) |tn| { - if (self.namespaces.contains(tn)) { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ tn, fa.field }); - const qualified_z = try self.allocator.dupeZ(u8, qualified); - if (self.generic_templates.contains(qualified) or - c.LLVMGetNamedFunction(self.module, qualified_z.ptr) != null) - { - var method_args = try self.allocator.alloc(*Node, call_node.args.len + 1); - method_args[0] = fa.object; - for (call_node.args, 0..) |arg, i| { - method_args[i + 1] = arg; - } - return self.genCallByName(qualified, .{ - .callee = call_node.callee, - .args = method_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 { - // Resolve UFCS alias: add :: ufcs num_add; → redirect "add" to "num_add" - if (self.ufcs_aliases.get(callee_name)) |resolved| { - return self.genCallByName(resolved, 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); - } - if (std.mem.eql(u8, callee_name, "closure")) { - return self.genClosureIntrinsic(call_node); - } - - // Local variables shadow imported functions: closures and function pointers - // take priority over generic templates, builtins, and LLVM named functions. - if (self.lookupValue(callee_name)) |v| { - const entry = v.asNamedValue(); - if (entry) |e| { - if (e.ty.isClosureType()) { - return self.genClosureCall(e, call_node); - } - if (e.ty.isFunctionType()) { - return self.genIndirectCall(e, call_node); - } - } - } - - // 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); - } - } - - 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)); - } - } - // Foreign rename fallback: sx name → C symbol name (e.g. "write_fd" → "write") - if (callee_fn == null) { - if (self.foreign_name_map.get(callee_name)) |c_name| { - var rbuf: [256]u8 = undefined; - callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf)); - } - } - if (callee_fn == null) { - 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); - if (num_params > 64) return self.emitErrorFmt("function has {d} parameters, exceeding maximum of 64", .{num_params}); - 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]); - - // Check if this is a Self-erased protocol param BEFORE generating the value - const is_self_erased = blk: { - if (!param_ty.isPointer() or !std.mem.eql(u8, param_ty.pointer_type.pointee_name, "void")) - break :blk false; - const dot_pos = std.mem.indexOfScalar(u8, callee_name, '.') orelse break :blk false; - const proto_name = callee_name[0..dot_pos]; - const pd = self.protocol_decls.get(proto_name) orelse break :blk false; - const method_name = callee_name[dot_pos + 1 ..]; - for (pd.methods) |pm| { - if (std.mem.eql(u8, pm.name, method_name)) { - // stored_param_types = [Proto, param0, param1, ...] - // call_node.args after genCall prepends self, so arg[0]=self, arg[1]=first method param - const method_param_idx = if (i > 0) i - 1 else break; - if (method_param_idx < pm.params.len) { - const pn = pm.params[method_param_idx]; - if (pn.data == .type_expr and std.mem.eql(u8, pn.data.type_expr.name, "Self")) - break :blk true; - } - break; - } - } - break :blk false; - }; - - // 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 (is_self_erased) blk: { - // Self-erased param: generate as actual type, then take address to pass as *void - const raw_val = try self.genExpr(arg); - if (!arg_ty.isPointer()) { - const tmp = self.buildEntryBlockAlloca(c.LLVMTypeOf(raw_val), "self_tmp"); - _ = c.LLVMBuildStore(self.builder, raw_val, tmp); - break :blk tmp; - } - break :blk raw_val; - } else 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; - const ptr_ty = self.ptrType(); - const fn_ptr = c.LLVMBuildLoad2(self.builder, ptr_ty, entry.ptr, "fn_ptr"); - return self.genIndirectCallFromPtr(fn_ptr, fti, call_node); - } - - fn genIndirectCallFromPtr(self: *CodeGen, fn_ptr: c.LLVMValueRef, fti: Type.FunctionTypeInfo, call_node: ast.Call) !c.LLVMValueRef { - // Build LLVM function type from FunctionTypeInfo - const ptr_ty_llvm = self.ptrType(); - if (fti.param_types.len > 64) return self.emitErrorFmt("indirect call has {d} parameters, exceeding maximum of 64", .{fti.param_types.len}); - 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 genClosureCall(self: *CodeGen, entry: NamedValue, call_node: ast.Call) !c.LLVMValueRef { - const cti = entry.ty.closure_type; - const closure_struct_ty = self.getClosureStructType(); - const closure_val = c.LLVMBuildLoad2(self.builder, closure_struct_ty, entry.ptr, "closure_load"); - return self.genClosureCallFromValue(closure_val, cti, call_node); - } - - fn genClosureCallFromValue(self: *CodeGen, closure_val: c.LLVMValueRef, cti: Type.ClosureTypeInfo, call_node: ast.Call) !c.LLVMValueRef { - // Extract fn_ptr and env from closure struct - const fn_ptr = self.extractValue(closure_val, 0, "cl_fn_ptr"); - const env_ptr = self.extractValue(closure_val, 1, "cl_env"); - - // Build LLVM function type: (env: *void, params...) -> R - const ptr_ty_llvm = self.ptrType(); - const total_params = cti.param_types.len + 1; // +1 for env - if (total_params > 64) return self.emitErrorFmt("closure call has {d} parameters, exceeding maximum of 64", .{total_params}); - var param_llvm_types: [64]c.LLVMTypeRef = undefined; - param_llvm_types[0] = ptr_ty_llvm; // env: *void - for (cti.param_types, 0..) |pt, i| { - param_llvm_types[i + 1] = if (pt.isArray()) ptr_ty_llvm else self.typeToLLVM(pt); - } - const ret_llvm = self.typeToLLVM(cti.return_type.*); - const fn_type = c.LLVMFunctionType( - ret_llvm, - ¶m_llvm_types, - @intCast(total_params), - 0, - ); - - // Generate arguments: [env, arg0, arg1, ...] - var arg_vals = std.ArrayList(c.LLVMValueRef).empty; - try arg_vals.append(self.allocator, env_ptr); - for (call_node.args, 0..) |arg, i| { - if (i < cti.param_types.len) { - const pt = cti.param_types[i]; - 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 { - 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 "cl_call"; - 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, - ); - } - - /// Check if a node refers to a named function (not a variable). - fn isFunctionName(self: *CodeGen, node: *Node) bool { - if (node.data != .identifier) return false; - const name = node.data.identifier.name; - // It's a function if LLVM knows about it and it's NOT a local variable - var nbuf: [256]u8 = undefined; - if (c.LLVMGetNamedFunction(self.module, self.nameToCStr(name, &nbuf)) != null) { - // Make sure it's not shadowed by a local variable - if (self.named_values.get(name) != null) return false; - return true; - } - return false; - } - - /// Auto-promote a bare function to a Closure by generating a static thunk. - /// The thunk has signature (env: *void, params...) -> R and ignores the env param. - /// Returns a closure value { thunk_ptr, null }. - fn promoteToClosureThunk(self: *CodeGen, node: *Node, cti: Type.ClosureTypeInfo) !c.LLVMValueRef { - // Determine source function name - const fn_name = blk: { - if (node.data == .identifier) break :blk node.data.identifier.name; - return self.emitError("auto-promotion to Closure requires a function name"); - }; - - // Check thunk cache for dedup - if (self.closure_thunks.get(fn_name)) |cached_thunk| { - // Build closure { thunk, null } - var closure_val = c.LLVMGetUndef(self.getClosureStructType()); - closure_val = self.insertValue(closure_val, cached_thunk, 0, "cl_fn"); - closure_val = self.insertValue(closure_val, c.LLVMConstPointerNull(self.ptrType()), 1, "cl_env"); - return closure_val; - } - - // Generate thunk: __thunk_(env: *void, params...) -> R - const thunk_name = try std.fmt.allocPrint(self.allocator, "__thunk_{s}", .{fn_name}); - const thunk_name_z = try self.allocator.dupeZ(u8, thunk_name); - - // Build thunk function type: (ptr, param_types...) -> ret_type - const ptr_ty = self.ptrType(); - const total_params = cti.param_types.len + 1; - var param_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, total_params); - param_llvm_types[0] = ptr_ty; // env: *void (ignored) - for (cti.param_types, 0..) |pt, i| { - param_llvm_types[i + 1] = if (pt.isArray()) ptr_ty else self.typeToLLVM(pt); - } - const ret_llvm = self.typeToLLVM(cti.return_type.*); - const thunk_fn_type = c.LLVMFunctionType( - ret_llvm, - param_llvm_types.ptr, - @intCast(total_params), - 0, - ); - - // Create the thunk function - const thunk_fn = c.LLVMAddFunction(self.module, thunk_name_z.ptr, thunk_fn_type); - c.LLVMSetLinkage(thunk_fn, c.LLVMPrivateLinkage); - - // Save current position - const saved_fn = self.current_function; - const saved_bb = self.getCurrentBlock(); - - // Build thunk body - const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, thunk_fn, "entry"); - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - self.current_function = thunk_fn; - - // Look up the original function - var nbuf: [256]u8 = undefined; - const orig_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(fn_name, &nbuf)) orelse - return self.emitErrorFmt("cannot find function '{s}' for closure promotion", .{fn_name}); - - // Build call to original: original(param0, param1, ...) — skip env (param 0 of thunk) - var call_args = try self.allocator.alloc(c.LLVMValueRef, cti.param_types.len); - for (0..cti.param_types.len) |i| { - call_args[i] = c.LLVMGetParam(thunk_fn, @intCast(i + 1)); - } - - const orig_fn_type = c.LLVMGlobalGetValueType(orig_fn); - const call_name: [*c]const u8 = if (ret_llvm == self.voidType()) "" else "fwd"; - const result = c.LLVMBuildCall2( - self.builder, - orig_fn_type, - orig_fn, - if (call_args.len > 0) call_args.ptr else null, - @intCast(call_args.len), - call_name, - ); - - if (ret_llvm == self.voidType()) { - _ = c.LLVMBuildRetVoid(self.builder); - } else { - // Convert result type if it doesn't match the thunk's declared return type - var ret_val = result; - const result_ty = c.LLVMTypeOf(result); - if (result_ty != ret_llvm) { - const src_kind = c.LLVMGetTypeKind(result_ty); - const dst_kind = c.LLVMGetTypeKind(ret_llvm); - if (src_kind == c.LLVMIntegerTypeKind and dst_kind == c.LLVMIntegerTypeKind) { - const src_bits = c.LLVMGetIntTypeWidth(result_ty); - const dst_bits = c.LLVMGetIntTypeWidth(ret_llvm); - if (src_bits > dst_bits) { - ret_val = c.LLVMBuildTrunc(self.builder, result, ret_llvm, "thunk_trunc"); - } else { - ret_val = c.LLVMBuildSExt(self.builder, result, ret_llvm, "thunk_sext"); - } - } - } - _ = c.LLVMBuildRet(self.builder, ret_val); - } - - // Restore position - self.current_function = saved_fn; - self.positionAt(saved_bb); - - // Cache thunk for dedup - self.closure_thunks.put(fn_name, thunk_fn) catch {}; - - // Build closure value { thunk_fn, null } - var closure_val = c.LLVMGetUndef(self.getClosureStructType()); - closure_val = self.insertValue(closure_val, thunk_fn, 0, "cl_fn"); - closure_val = self.insertValue(closure_val, c.LLVMConstPointerNull(self.ptrType()), 1, "cl_env"); - return closure_val; - } - - // Counter for unique closure names - var closure_counter: u32 = 0; - - /// closure(lambda) intrinsic — captures free variables, allocates env, returns Closure. - fn genClosureIntrinsic(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { - if (call_node.args.len != 1) return self.emitError("closure() requires exactly one lambda argument"); - const arg = call_node.args[0]; - const lambda = switch (arg.data) { - .lambda => arg.data.lambda, - else => return self.emitError("closure() argument must be a lambda expression"), - }; - - // Check if any params need type inference - const has_inferred_params = for (lambda.params) |p| { - if (p.type_expr.data == .inferred_type) break true; - } else false; - - // Determine lambda return type - const ret_ty = if (lambda.return_type) |rt| - self.resolveType(rt) - else if (has_inferred_params) - (if (self.closure_expected_type) |ctx| ctx.return_type.* else Type.void_type) - else if (lambda.body.data == .block) - Type.void_type - else - self.inferType(lambda.body); - - // Collect parameter names for exclusion - var param_names = std.StringHashMap(void).init(self.allocator); - for (lambda.params) |p| { - param_names.put(p.name, {}) catch {}; - } - - // Free variable analysis: walk body, collect identifiers that are local variables (not params, not functions, not globals) - var captures = std.ArrayList(CaptureInfo).empty; - try self.collectCaptures(lambda.body, ¶m_names, &captures); - - // Deduplicate captures - var seen = std.StringHashMap(void).init(self.allocator); - var deduped = std.ArrayList(CaptureInfo).empty; - for (captures.items) |cap| { - if (!seen.contains(cap.name)) { - seen.put(cap.name, {}) catch {}; - try deduped.append(self.allocator, cap); - } - } - const capture_list = deduped.items; - - // Build param types for the closure type (resolve inferred from context) - var closure_param_types = try self.allocator.alloc(Type, lambda.params.len); - for (lambda.params, 0..) |p, i| { - if (p.type_expr.data == .inferred_type) { - if (self.closure_expected_type) |ctx| { - if (i < ctx.param_types.len) { - closure_param_types[i] = ctx.param_types[i]; - } else return self.emitError("closure has more parameters than expected type"); - } else return self.emitError("cannot infer closure parameter type without type context"); - } else { - closure_param_types[i] = self.resolveType(p.type_expr); - } - } - - // Generate unique name - const closure_id = closure_counter; - closure_counter += 1; - const tramp_name = try std.fmt.allocPrint(self.allocator, "__closure_{d}", .{closure_id}); - const tramp_name_z = try self.allocator.dupeZ(u8, tramp_name); - - // Build env struct type: { capture0_type, capture1_type, ... } - var env_field_types = try self.allocator.alloc(c.LLVMTypeRef, capture_list.len); - for (capture_list, 0..) |cap, i| { - env_field_types[i] = self.typeToLLVM(cap.ty); - } - const env_struct_ty = c.LLVMStructTypeInContext( - self.context, - if (env_field_types.len > 0) env_field_types.ptr else null, - @intCast(env_field_types.len), - 0, - ); - - // Build trampoline function type: (env: *void, params...) -> R - const ptr_ty = self.ptrType(); - const total_params = lambda.params.len + 1; - var tramp_param_types = try self.allocator.alloc(c.LLVMTypeRef, total_params); - tramp_param_types[0] = ptr_ty; // env: *void - for (closure_param_types, 0..) |pt, i| { - tramp_param_types[i + 1] = if (pt.isArray()) ptr_ty else self.typeToLLVM(pt); - } - const ret_llvm = self.typeToLLVM(ret_ty); - const tramp_fn_type = c.LLVMFunctionType( - ret_llvm, - tramp_param_types.ptr, - @intCast(total_params), - 0, - ); - - // Create trampoline function - const tramp_fn = c.LLVMAddFunction(self.module, tramp_name_z.ptr, tramp_fn_type); - c.LLVMSetLinkage(tramp_fn, c.LLVMPrivateLinkage); - - // Save codegen 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; - const saved_narrowed = self.narrowed_types; - - // Set up trampoline body - self.current_function = tramp_fn; - self.current_return_type = ret_ty; - self.named_values = std.StringHashMap(NamedValue).init(self.allocator); - self.narrowed_types = std.StringHashMap(NarrowedInfo).init(self.allocator); - - const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, tramp_fn, "entry"); - c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); - - // Load env pointer (param 0) and cast to env struct type - const raw_env = c.LLVMGetParam(tramp_fn, 0); - - // Register captured variables by loading from env struct - for (capture_list, 0..) |cap, i| { - const field_gep = self.structGEP(env_struct_ty, raw_env, @intCast(i), "cap_ptr"); - const field_llvm_ty = self.typeToLLVM(cap.ty); - // For aggregate types (strings, slices, structs), use alloca - if (cap.ty == .string_type or cap.ty.isSlice() or cap.ty.isStruct() or cap.ty.isClosureType()) { - // Alloca + copy for aggregate types - const alloca = self.buildEntryBlockAlloca(field_llvm_ty, "cap_alloca"); - const loaded = c.LLVMBuildLoad2(self.builder, field_llvm_ty, field_gep, "cap_load"); - _ = c.LLVMBuildStore(self.builder, loaded, alloca); - try self.named_values.put(cap.name, .{ .ptr = alloca, .ty = cap.ty }); - } else { - // Scalar: just use the GEP as the alloca - try self.named_values.put(cap.name, .{ .ptr = field_gep, .ty = cap.ty }); - } - } - - // Register lambda params (starting from param index 1) - for (lambda.params, 0..) |p, i| { - const param_ty = closure_param_types[i]; - const llvm_param = c.LLVMGetParam(tramp_fn, @intCast(i + 1)); - const param_llvm_ty = self.typeToLLVM(param_ty); - const alloca = self.buildEntryBlockAlloca(param_llvm_ty, "param"); - _ = c.LLVMBuildStore(self.builder, llvm_param, alloca); - try self.named_values.put(p.name, .{ .ptr = alloca, .ty = param_ty }); - } - - // Generate lambda body - if (lambda.body.data == .block) { - // Block body — generate statements - for (lambda.body.data.block.stmts) |stmt| { - _ = try self.genStmt(stmt); - } - // Add terminator if current block (possibly a dead `after_ret` block) lacks one - if (c.LLVMGetBasicBlockTerminator(self.getCurrentBlock()) == null) { - if (ret_ty == .void_type) { - _ = c.LLVMBuildRetVoid(self.builder); - } else { - // Dead block after explicit return — add unreachable - _ = c.LLVMBuildUnreachable(self.builder); - } - } - } else { - // Expression body - const body_val = try self.genExprAsType(lambda.body, ret_ty); - _ = c.LLVMBuildRet(self.builder, body_val); - } - - // Restore codegen state - self.named_values.deinit(); - self.named_values = saved_named; - self.narrowed_types = saved_narrowed; - self.current_return_type = saved_ret; - self.current_function = saved_fn; - self.positionAt(saved_bb); - - // Now back in the caller's context — allocate env and store captures - if (capture_list.len > 0) { - // Allocate env via context.allocator - const env_size = c.LLVMSizeOf(env_struct_ty); - const env_size_i64 = c.LLVMBuildIntCast2(self.builder, env_size, self.i64Type(), 0, "env_size"); - const env_raw = try self.emitContextAlloc(env_size_i64); - - // Store captured values into env struct - for (capture_list, 0..) |cap, i| { - const gep = self.structGEP(env_struct_ty, env_raw, @intCast(i), "env_store"); - _ = c.LLVMBuildStore(self.builder, cap.value, gep); - } - - // Build closure value { tramp_fn, env_raw } - var closure_val = c.LLVMGetUndef(self.getClosureStructType()); - closure_val = self.insertValue(closure_val, tramp_fn, 0, "cl_fn"); - closure_val = self.insertValue(closure_val, env_raw, 1, "cl_env"); - return closure_val; - } else { - // No captures — null env (like auto-promotion) - var closure_val = c.LLVMGetUndef(self.getClosureStructType()); - closure_val = self.insertValue(closure_val, tramp_fn, 0, "cl_fn"); - closure_val = self.insertValue(closure_val, c.LLVMConstPointerNull(ptr_ty), 1, "cl_env"); - return closure_val; - } - } - - const CaptureInfo = struct { - name: []const u8, - ty: Type, - value: c.LLVMValueRef, // loaded value at capture site - }; - - /// Walk AST node and collect free variable references (identifiers that are in scope as local vars, - /// not lambda params, not functions, not type names). - fn collectCaptures(self: *CodeGen, node: *Node, param_names: *std.StringHashMap(void), captures: *std.ArrayList(CaptureInfo)) !void { - switch (node.data) { - .identifier => |id| { - // Skip if it's a lambda param - if (param_names.contains(id.name)) return; - // Skip if it's a function name - if (self.isFunctionName(node)) return; - // Skip if it's a type name - if (self.type_registry.contains(id.name)) return; - // Skip if it's a generic template - if (self.generic_templates.contains(id.name)) return; - // Skip if it's a namespace - if (self.namespaces.contains(id.name)) return; - // Skip if it's a library constant - if (self.library_constants.contains(id.name)) return; - // Only capture if it's a local/global variable - if (self.getNamedOrGlobal(id.name)) |entry| { - // Load the current value - const val = self.loadTyped(entry.ty, entry.ptr, "capture"); - try captures.append(self.allocator, .{ - .name = id.name, - .ty = entry.ty, - .value = val, - }); - } - }, - .block => |blk| { - for (blk.stmts) |stmt| { - try self.collectCaptures(stmt, param_names, captures); - } - }, - .binary_op => |bop| { - try self.collectCaptures(bop.lhs, param_names, captures); - try self.collectCaptures(bop.rhs, param_names, captures); - }, - .unary_op => |uop| { - try self.collectCaptures(uop.operand, param_names, captures); - }, - .call => |call| { - try self.collectCaptures(call.callee, param_names, captures); - for (call.args) |a| { - try self.collectCaptures(a, param_names, captures); - } - }, - .field_access => |fa| { - try self.collectCaptures(fa.object, param_names, captures); - }, - .if_expr => |ie| { - try self.collectCaptures(ie.condition, param_names, captures); - try self.collectCaptures(ie.then_branch, param_names, captures); - if (ie.else_branch) |eb| { - try self.collectCaptures(eb, param_names, captures); - } - }, - .return_stmt => |rs| { - if (rs.value) |val| { - try self.collectCaptures(val, param_names, captures); - } - }, - .while_expr => |we| { - try self.collectCaptures(we.condition, param_names, captures); - try self.collectCaptures(we.body, param_names, captures); - }, - .for_expr => |fe| { - try self.collectCaptures(fe.iterable, param_names, captures); - // Add loop capture name to excluded params - param_names.put(fe.capture_name, {}) catch {}; - if (fe.index_name) |idx_name| { - param_names.put(idx_name, {}) catch {}; - } - try self.collectCaptures(fe.body, param_names, captures); - }, - .var_decl => |vd| { - if (vd.value) |val| { - try self.collectCaptures(val, param_names, captures); - } - // After declaration, the name is local — exclude from captures - param_names.put(vd.name, {}) catch {}; - }, - .const_decl => |cd| { - try self.collectCaptures(cd.value, param_names, captures); - param_names.put(cd.name, {}) catch {}; - }, - .assignment => |asgn| { - try self.collectCaptures(asgn.target, param_names, captures); - try self.collectCaptures(asgn.value, param_names, captures); - }, - .index_expr => |ie| { - try self.collectCaptures(ie.object, param_names, captures); - try self.collectCaptures(ie.index, param_names, captures); - }, - .struct_literal => |sl| { - for (sl.field_inits) |fi| { - try self.collectCaptures(fi.value, param_names, captures); - } - }, - .deref_expr => |de| { - try self.collectCaptures(de.operand, param_names, captures); - }, - .force_unwrap => |fu| { - try self.collectCaptures(fu.operand, param_names, captures); - }, - .null_coalesce => |nc| { - try self.collectCaptures(nc.lhs, param_names, captures); - try self.collectCaptures(nc.rhs, param_names, captures); - }, - .match_expr => |me| { - try self.collectCaptures(me.subject, param_names, captures); - for (me.arms) |arm| { - if (arm.pattern) |pat| { - try self.collectCaptures(pat, param_names, captures); - } - try self.collectCaptures(arm.body, param_names, captures); - } - }, - .defer_stmt => |ds| { - try self.collectCaptures(ds.expr, param_names, captures); - }, - .slice_expr => |se| { - try self.collectCaptures(se.object, param_names, captures); - if (se.start) |s| try self.collectCaptures(s, param_names, captures); - if (se.end) |e| try self.collectCaptures(e, param_names, captures); - }, - .spread_expr => |se| { - try self.collectCaptures(se.operand, param_names, captures); - }, - .lambda => |lam| { - // Nested lambda: its params are excluded, but captures from outer scope bubble up - for (lam.params) |p| { - param_names.put(p.name, {}) catch {}; - } - try self.collectCaptures(lam.body, param_names, captures); - }, - .chained_comparison => |cc| { - for (cc.operands) |operand| { - try self.collectCaptures(operand, param_names, captures); - } - }, - // Leaf nodes: nothing to capture - .int_literal, .float_literal, .bool_literal, .string_literal, - .null_literal, .undef_literal, .inferred_type, .builtin_expr, .break_expr, - .continue_expr, .type_expr, .enum_literal, .foreign_expr, - .library_decl, .array_type_expr, .slice_type_expr, - .pointer_type_expr, .many_pointer_type_expr, .optional_type_expr, - .function_type_expr, .closure_type_expr, .tuple_type_expr, - => {}, - // Remaining nodes that contain children - .array_literal => |al| { - for (al.elements) |elem| { - try self.collectCaptures(elem, param_names, captures); - } - }, - .tuple_literal => |tl| { - for (tl.elements) |elem| { - try self.collectCaptures(elem.value, param_names, captures); - } - }, - .comptime_expr => |ct| { - try self.collectCaptures(ct.expr, param_names, captures); - }, - .insert_expr => |ins| { - try self.collectCaptures(ins.expr, param_names, captures); - }, - .push_stmt => |ps| { - try self.collectCaptures(ps.context_expr, param_names, captures); - try self.collectCaptures(ps.body, param_names, captures); - }, - .multi_assign => |ma| { - for (ma.targets) |t| try self.collectCaptures(t, param_names, captures); - for (ma.values) |v| try self.collectCaptures(v, param_names, captures); - }, - // Top-level decls: skip - .root, .fn_decl, .param, .match_arm, .enum_decl, .struct_decl, - .union_decl, .namespace_decl, .import_decl, .c_import_decl, - .ufcs_alias, .parameterized_type_expr, - .protocol_decl, .impl_block, - => {}, - } - } - - /// Allocate memory via context.allocator. Panics at runtime if no allocator is set. - /// Emits: context.allocator.alloc_fn(context.allocator.ctx, size) - fn emitContextAlloc(self: *CodeGen, size_val: c.LLVMValueRef) !c.LLVMValueRef { - // Look up the 'context' global - const ctx_entry = self.global_mutable_vars.get("context") orelse - return self.emitError("closure() requires a global 'context' variable (from std.sx) with an allocator"); - - // Look up struct layout for Context and Allocator - const ctx_info = self.lookupStructInfo("Context") orelse - return self.emitError("closure() requires 'Context' struct type"); - const alloc_info = self.lookupStructInfo("Allocator") orelse - return self.emitError("closure() requires 'Allocator' struct type"); - - // Find field indices - const alloc_field_idx: c_uint = for (ctx_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "allocator")) break @intCast(i); - } else return self.emitError("Context struct missing 'allocator' field"); - - const ctx_field_idx: c_uint = for (alloc_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "ctx")) break @intCast(i); - } else return self.emitError("Allocator struct missing 'ctx' field"); - - const alloc_fn_field_idx: c_uint = for (alloc_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "alloc") or std.mem.eql(u8, fname, "alloc_fn")) break @intCast(i); - } else return self.emitError("Allocator struct missing 'alloc' field"); - - // GEP to context.allocator - const alloc_ptr = self.structGEP(ctx_info.llvm_type, ctx_entry.ptr, alloc_field_idx, "ctx_alloc_ptr"); - - // Load allocator.ctx and allocator.alloc_fn - const alloc_ctx_ptr = self.structGEP(alloc_info.llvm_type, alloc_ptr, ctx_field_idx, "alloc_ctx_ptr"); - const alloc_ctx = c.LLVMBuildLoad2(self.builder, self.ptrType(), alloc_ctx_ptr, "alloc_ctx"); - const alloc_fn_ptr = self.structGEP(alloc_info.llvm_type, alloc_ptr, alloc_fn_field_idx, "alloc_fn_ptr"); - const alloc_fn = c.LLVMBuildLoad2(self.builder, self.ptrType(), alloc_fn_ptr, "alloc_fn"); - - // Check allocator.ctx != null — panic if no allocator set - const is_null = c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, alloc_ctx, c.LLVMConstNull(self.ptrType()), "alloc_null"); - const cur_fn = self.current_function; - const then_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "alloc_panic"); - const cont_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "alloc_ok"); - _ = c.LLVMBuildCondBr(self.builder, is_null, then_bb, cont_bb); - - // Panic block: call trap - c.LLVMPositionBuilderAtEnd(self.builder, then_bb); - const trap_fn = self.getOrDeclareIntrinsic("llvm.trap"); - _ = c.LLVMBuildCall2(self.builder, c.LLVMFunctionType(self.voidType(), null, 0, 0), trap_fn, null, 0, ""); - _ = c.LLVMBuildUnreachable(self.builder); - - // Continue: call alloc_fn(ctx, size) - c.LLVMPositionBuilderAtEnd(self.builder, cont_bb); - var fn_param_types = [_]c.LLVMTypeRef{ self.ptrType(), self.i64Type() }; - const fn_type = c.LLVMFunctionType(self.ptrType(), &fn_param_types, 2, 0); - var call_args = [_]c.LLVMValueRef{ alloc_ctx, size_val }; - return c.LLVMBuildCall2(self.builder, fn_type, alloc_fn, &call_args, 2, "env_alloc"); - } - - fn getOrDeclareIntrinsic(self: *CodeGen, name: [*c]const u8) c.LLVMValueRef { - if (c.LLVMGetNamedFunction(self.module, name)) |f| return f; - const fn_type = c.LLVMFunctionType(self.voidType(), null, 0, 0); - return c.LLVMAddFunction(self.module, name, fn_type); - } - - /// Find an LLVM function by name, trying bare name first then namespaced variants. - fn findFunction(self: *CodeGen, name: []const u8) ?c.LLVMValueRef { - // Try bare name first - var nbuf: [256]u8 = undefined; - const name_z = std.fmt.bufPrintZ(&nbuf, "{s}", .{name}) catch return null; - if (c.LLVMGetNamedFunction(self.module, name_z.ptr)) |f| return f; - // Try each registered namespace prefix - var ns_it = self.namespaces.iterator(); - while (ns_it.next()) |entry| { - var qbuf: [256]u8 = undefined; - const qualified = std.fmt.bufPrintZ(&qbuf, "{s}.{s}", .{ entry.key_ptr.*, name }) catch continue; - if (c.LLVMGetNamedFunction(self.module, qualified.ptr)) |f| return f; - } - return null; - } - - /// Auto-initialize the global `context` with a default GPA allocator. - /// Called at the start of main(). No-op if std.sx types are not imported. - fn emitDefaultContextInit(self: *CodeGen) !void { - // Look up context global — bail if not present (no std.sx) - const ctx_entry = self.global_mutable_vars.get("context") orelse return; - - // Look up struct layouts — bail if missing - const ctx_info = self.lookupStructInfo("Context") orelse return; - const alloc_info = self.lookupStructInfo("Allocator") orelse return; - const gpa_info = self.lookupStructInfo("GPA") orelse return; - - // Look up GPA→Allocator thunk functions (protocol-generated) - const gpa_alloc_fn = self.findFunction("__GPA_Allocator_alloc") orelse - self.findFunction("gpa_alloc") orelse return; - const gpa_free_fn = self.findFunction("__GPA_Allocator_dealloc") orelse - self.findFunction("gpa_free") orelse return; - - // Find field indices in Context - const alloc_field_idx: c_uint = for (ctx_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "allocator")) break @intCast(i); - } else return; - const data_field_idx: c_uint = for (ctx_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "data")) break @intCast(i); - } else return; - - // Find field indices in Allocator (protocol-generated: ctx, alloc, dealloc) - const ctx_field_idx: c_uint = for (alloc_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "ctx")) break @intCast(i); - } else return; - const alloc_fn_field_idx: c_uint = for (alloc_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "alloc") or std.mem.eql(u8, fname, "alloc_fn")) break @intCast(i); - } else return; - const free_fn_field_idx: c_uint = for (alloc_info.field_names, 0..) |fname, i| { - if (std.mem.eql(u8, fname, "dealloc") or std.mem.eql(u8, fname, "free_fn")) break @intCast(i); - } else return; - - // 1. Stack-allocate GPA struct and zero alloc_count - const gpa_alloca = self.buildEntryBlockAlloca(gpa_info.llvm_type, "__default_gpa"); - self.storeStructField(gpa_info.llvm_type, gpa_alloca, 0, c.LLVMConstInt(self.i64Type(), 0, 0)); - - // 2. GEP to context.allocator - const alloc_ptr = self.structGEP(ctx_info.llvm_type, ctx_entry.ptr, alloc_field_idx, "ctx_alloc"); - - // 3. Store allocator.ctx = &gpa - self.storeStructField(alloc_info.llvm_type, alloc_ptr, ctx_field_idx, gpa_alloca); - - // 4. Store allocator.alloc_fn = @gpa_alloc - self.storeStructField(alloc_info.llvm_type, alloc_ptr, alloc_fn_field_idx, gpa_alloc_fn); - - // 5. Store allocator.free_fn = @gpa_free - self.storeStructField(alloc_info.llvm_type, alloc_ptr, free_fn_field_idx, gpa_free_fn); - - // 6. Store context.data = null - const data_ptr = self.structGEP(ctx_info.llvm_type, ctx_entry.ptr, data_field_idx, "ctx_data"); - _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(self.ptrType()), data_ptr); - } - - fn getOrDeclareMalloc(self: *CodeGen) c.LLVMValueRef { - var nbuf: [256]u8 = undefined; - if (c.LLVMGetNamedFunction(self.module, self.nameToCStr("malloc", &nbuf))) |f| return f; - var param_types = [_]c.LLVMTypeRef{self.i64Type()}; - const fn_type = c.LLVMFunctionType( - self.ptrType(), - ¶m_types, - 1, - 0, - ); - return c.LLVMAddFunction(self.module, "malloc", fn_type); - } - - 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); - // Bind explicit $T: Type params from type expression args - for (fd.params, 0..) |param, i| { - if (!param.is_comptime) continue; - if (i >= call_node.args.len) continue; - const arg = call_node.args[i]; - if (arg.data != .type_expr) continue; - for (fd.type_params) |tp| { - if (std.mem.eql(u8, tp.name, param.name)) { - const constraint = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; - if (std.mem.eql(u8, constraint, "Type")) { - try bindings.put(tp.name, self.resolveType(arg)); - } - break; - } - } - } - 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()) - self.resolveTypeFromName(arg_ty.array_type.element_name) orelse arg_ty - else if (arg_ty.isSlice()) - self.resolveTypeFromName(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; - } - } - } - } - } - - // Check protocol constraints: $T/Eq/Hashable → verify T implements Eq and Hashable - for (fd.type_params) |tp| { - if (tp.protocol_constraints.len > 0) { - if (bindings.get(tp.name)) |bound_ty| { - const type_name = bound_ty.toName() orelse "unknown"; - for (tp.protocol_constraints) |proto_name| { - const key = try std.fmt.allocPrint(self.allocator, "{s}\x00{s}", .{ proto_name, type_name }); - if (!self.impl_blocks.contains(key)) { - return self.emitErrorFmt("{s} does not implement {s}", .{ type_name, proto_name }); - } - } - } - } - } - - 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 - // Skip $T: Type params (arg is a type expression, not a runtime value) - const saved_call_bindings = self.type_param_bindings; - self.type_param_bindings = bindings; - var arg_vals = std.ArrayList(c.LLVMValueRef).empty; - // Use separate indices for call args and fn params, since $T: Type params - // may or may not have a corresponding arg (explicit type vs inferred) - var arg_idx: usize = 0; - for (fd.params) |p| { - if (isTypeParamDecl(p)) { - // If the arg at this position is a type expression, skip it (explicitly passed type) - if (arg_idx < call_node.args.len and call_node.args[arg_idx].data == .type_expr) { - arg_idx += 1; - } - // Otherwise, T was inferred — no arg to consume - continue; - } - if (p.is_comptime) { - // Comptime value param — skip (handled in genComptimeCall) - if (arg_idx < call_node.args.len) arg_idx += 1; - continue; - } - if (arg_idx < call_node.args.len) { - const param_ty = self.resolveType(p.type_expr); - try arg_vals.append(self.allocator, try self.genExprAsType(call_node.args[arg_idx], param_ty)); - } - arg_idx += 1; - } - 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 = null; - var any_val_node: ?*Node = null; - 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 orelse return self.emitError("runtime dispatch requires a cast() argument")); - const any_val = try self.genExpr(any_val_node orelse return self.emitError("runtime dispatch requires a cast() argument")); - // 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()) - self.resolveTypeFromName(sx_type.slice_type.element_name) orelse sx_type - else if (sx_type.isArray()) - self.resolveTypeFromName(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| { - // Skip $T: Type params — type is resolved via bindings, not passed at runtime - if (isTypeParamDecl(param)) continue; - 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| { - const ret_val = try self.prepareReturnValue(val, ret_sx_type); - self.ret(ret_val); - } 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; - } - - /// Result of detecting a null-check pattern in a condition expression - const NullCheck = struct { - var_name: []const u8, - is_eq: bool, // true for == null, false for != null - }; - - /// Detect a single `x == null` or `x != null` pattern in a condition expression - fn detectNullCheck(self: *CodeGen, cond: *Node) ?NullCheck { - if (cond.data != .binary_op) return null; - const bop = cond.data.binary_op; - if (bop.op != .eq and bop.op != .neq) return null; - - // Check: identifier op null_literal OR null_literal op identifier - const var_name: ?[]const u8 = if (bop.lhs.data == .identifier and bop.rhs.data == .null_literal) - bop.lhs.data.identifier.name - else if (bop.lhs.data == .null_literal and bop.rhs.data == .identifier) - bop.rhs.data.identifier.name - else - null; - - const name = var_name orelse return null; - - // Verify the variable is actually optional - if (self.named_values.get(name)) |entry| { - if (entry.ty.isOptional()) { - return NullCheck{ - .var_name = name, - .is_eq = bop.op == .eq, - }; - } - } - return null; - } - - /// Collect null checks from compound conditions: - /// `x != null && y != null` → [x(!=), y(!=)] — narrow both in then-branch - /// `x == null || y == null` → [x(==), y(==)] — narrow both after guard - /// Only collects when ALL leaves are null checks with the SAME polarity connected - /// by the expected operator (&& for !=null, || for ==null). - fn collectNullChecks(self: *CodeGen, cond: *Node, buf: []NullCheck) usize { - // Try single null check first - if (self.detectNullCheck(cond)) |nc| { - buf[0] = nc; - return 1; - } - // Try compound: binary_op with and_op or or_op - if (cond.data != .binary_op) return 0; - const bop = cond.data.binary_op; - if (bop.op != .and_op and bop.op != .or_op) return 0; - - var left_buf: [8]NullCheck = undefined; - var right_buf: [8]NullCheck = undefined; - const left_n = self.collectNullChecks(bop.lhs, &left_buf); - const right_n = self.collectNullChecks(bop.rhs, &right_buf); - if (left_n == 0 or right_n == 0) return 0; - if (left_n + right_n > buf.len) return 0; - - // All checks must have same polarity: - // && chains: all must be != null (is_eq=false) - // || chains: all must be == null (is_eq=true) - const expected_eq = bop.op == .or_op; // || → ==null, && → !=null - for (left_buf[0..left_n]) |nc| { - if (nc.is_eq != expected_eq) return 0; - } - for (right_buf[0..right_n]) |nc| { - if (nc.is_eq != expected_eq) return 0; - } - - @memcpy(buf[0..left_n], left_buf[0..left_n]); - @memcpy(buf[left_n..][0..right_n], right_buf[0..right_n]); - return left_n + right_n; - } - - /// Push a narrowing: load the optional, extract payload, store in temp alloca - fn pushNarrowing(self: *CodeGen, var_name: []const u8) !void { - const entry = self.named_values.get(var_name) orelse return; - const opt_ty = entry.ty; - if (!opt_ty.isOptional()) return; - - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse return; - const child_llvm_ty = self.typeToLLVM(child_ty); - - // Load the optional value and extract the payload - const opt_llvm_ty = self.typeToLLVM(opt_ty); - const opt_val = c.LLVMBuildLoad2(self.builder, opt_llvm_ty, entry.ptr, "narrow_load"); - const payload = self.optionalPayload(opt_val, opt_ty); - - // Store payload in a temp alloca - const alloca = self.buildEntryBlockAlloca(child_llvm_ty, "narrowed"); - _ = c.LLVMBuildStore(self.builder, payload, alloca); - - try self.narrowed_types.put(var_name, .{ - .narrowed_ty = child_ty, - .payload_ptr = alloca, - }); - } - - /// Pop a narrowing entry - fn popNarrowing(self: *CodeGen, var_name: []const u8) void { - _ = self.narrowed_types.remove(var_name); - } - - fn genIfExpr(self: *CodeGen, if_expr: ast.IfExpr) !c.LLVMValueRef { - // Optional binding: if val := expr { ... } - if (if_expr.binding_name) |binding_name| { - const opt_val = try self.genExpr(if_expr.condition); - const opt_ty = self.inferType(if_expr.condition); - if (!opt_ty.isOptional()) return self.emitError("'if val := expr' requires an optional expression"); - - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse - return self.emitError("unknown optional inner type"); - - const has_val = self.optionalHasValue(opt_val, opt_ty); - const has_else = if_expr.else_branch != null; - - var then_bb = self.appendBB("if_some"); - var else_bb: c.LLVMBasicBlockRef = if (has_else) self.appendBB("if_none") else null; - const merge_bb = self.appendBB("if_merge"); - - const false_dest = if (has_else) else_bb else merge_bb; - self.condBr(has_val, then_bb, false_dest); - - // Then branch: bind the unwrapped value - self.positionAt(then_bb); - const payload = self.optionalPayload(opt_val, opt_ty); - const alloca = try self.buildNamedAlloca(self.typeToLLVM(child_ty), binding_name); - _ = c.LLVMBuildStore(self.builder, payload, alloca); - try self.registerVariable(binding_name, alloca, child_ty); - const then_val = try self.genExpr(if_expr.then_branch); - then_bb = self.getCurrentBlock(); - 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); - } - - self.positionAt(merge_bb); - - 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; - } - - // Detect null-check narrowing: if x != null { ... } or if x == null { ... } - // Also handles compound: if x != null && y != null { ... } - var null_checks_buf: [8]NullCheck = undefined; - const null_check_count = self.collectNullChecks(if_expr.condition, &null_checks_buf); - const null_checks = null_checks_buf[0..null_check_count]; - - // 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 — apply narrowing for != null checks (including && chains) - self.positionAt(then_bb); - for (null_checks) |nc| { - if (!nc.is_eq) { // x != null → narrow in then - try self.pushNarrowing(nc.var_name); - } - } - const then_val = try self.genExpr(if_expr.then_branch); - for (null_checks) |nc| { - if (!nc.is_eq) self.popNarrowing(nc.var_name); - } - then_bb = self.getCurrentBlock(); // may have changed due to nested control flow - self.br(merge_bb); - - // Else branch — apply narrowing for == null checks (x is non-null in else) - var else_val: c.LLVMValueRef = null; - if (if_expr.else_branch) |else_branch| { - self.positionAt(else_bb); - for (null_checks) |nc| { - if (nc.is_eq) { // x == null → narrow in else - try self.pushNarrowing(nc.var_name); - } - } - else_val = try self.genExpr(else_branch); - for (null_checks) |nc| { - if (nc.is_eq) self.popNarrowing(nc.var_name); - } - else_bb = self.getCurrentBlock(); - self.br(merge_bb); - } - - // Merge block - self.positionAt(merge_bb); - - // Guard narrowing: if x == null { return; } → x narrowed after - // Also handles: if x == null || y == null { return; } → both narrowed after - if (!has_else and null_check_count > 0 and bodyAlwaysExits(if_expr.then_branch)) { - for (null_checks) |nc| { - if (nc.is_eq) { - try self.pushNarrowing(nc.var_name); - // Persists for rest of enclosing block, cleaned up at function boundary - } - } - } - - // 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; - } - - /// Check if a body expression unconditionally exits the current scope - fn bodyAlwaysExits(body: *Node) bool { - if (body.data == .return_stmt) return true; - if (body.data == .break_expr) return true; - if (body.data == .continue_expr) return true; - if (body.data == .block) { - const stmts = body.data.block.stmts; - if (stmts.len > 0) { - return bodyAlwaysExits(stmts[stmts.len - 1]); - } - } - return false; - } - - 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); - - // Optional binding: while val := expr { ... } - if (while_expr.binding_name) |binding_name| { - const opt_val = try self.genExpr(while_expr.condition); - const opt_ty = self.inferType(while_expr.condition); - if (!opt_ty.isOptional()) return self.emitError("'while val := expr' requires an optional expression"); - - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse - return self.emitError("unknown optional inner type"); - - const has_val = self.optionalHasValue(opt_val, opt_ty); - self.condBr(has_val, body_bb, after_bb); - - // Body block: bind the unwrapped value - 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; - - const payload = self.optionalPayload(opt_val, opt_ty); - const alloca = try self.buildNamedAlloca(self.typeToLLVM(child_ty), binding_name); - _ = c.LLVMBuildStore(self.builder, payload, alloca); - try self.registerVariable(binding_name, alloca, child_ty); - - _ = try self.genExpr(while_expr.body); - - self.loop_break_bb = saved_break_bb; - self.loop_continue_bb = saved_continue_bb; - - const current_bb = self.getCurrentBlock(); - if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { - self.br(cond_bb); - } - - self.positionAt(after_bb); - return null; - } - - 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 genPushStmt(self: *CodeGen, ps: ast.PushStmt) !c.LLVMValueRef { - // Look up the 'context' global mutable variable - const ctx_entry = self.global_mutable_vars.get("context") orelse - return self.emitError("push requires a global 'context' variable"); - const ctx_ty = ctx_entry.ty; - const llvm_ty = self.typeToLLVM(ctx_ty); - - // Save current context value - const saved = c.LLVMBuildLoad2(self.builder, llvm_ty, ctx_entry.ptr, "saved_ctx"); - - // Evaluate new context expression and store to global - const new_ctx = try self.genExprAsType(ps.context_expr, ctx_ty); - _ = c.LLVMBuildStore(self.builder, new_ctx, ctx_entry.ptr); - - // Generate body - _ = try self.genExpr(ps.body); - - // Restore saved context - _ = c.LLVMBuildStore(self.builder, saved, ctx_entry.ptr); - - 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 = self.resolveTypeFromName(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 = self.resolveTypeFromName(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.registerVariable(idx_name, idx_alloca, Type.s(64)); - } - } - - // 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.saveShadowed(for_expr.capture_name); - 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; - } - - /// Generate match expression for optional types: case .some: (val) { ... } case .none: { ... } - fn genOptionalMatch(self: *CodeGen, match: ast.MatchExpr, opt_ty: Type) !c.LLVMValueRef { - const opt_val = try self.genExpr(match.subject); - const has_val = self.optionalHasValue(opt_val, opt_ty); - - const merge_bb = self.appendBB("opt_match_end"); - const some_bb = self.appendBB("opt_some"); - const none_bb = self.appendBB("opt_none"); - - // Find .some and .none arms - var some_arm: ?ast.MatchArm = null; - var none_arm: ?ast.MatchArm = null; - var else_arm: ?ast.MatchArm = null; - for (match.arms) |arm| { - if (arm.pattern) |pat| { - if (pat.data == .enum_literal) { - if (std.mem.eql(u8, pat.data.enum_literal.name, "some")) { - some_arm = arm; - } else if (std.mem.eql(u8, pat.data.enum_literal.name, "none")) { - none_arm = arm; - } - } - } else { - else_arm = arm; - } - } - - // Branch on has_value: 1 = some, 0 = none - _ = c.LLVMBuildCondBr(self.builder, has_val, some_bb, none_bb); - - var phi_vals = std.ArrayList(c.LLVMValueRef).empty; - var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; - var has_result = false; - var result_type: c.LLVMTypeRef = null; - - // Generate .some arm - self.positionAt(some_bb); - const some_val = blk: { - if (some_arm) |arm| { - if (arm.is_break) { - self.br(merge_bb); - break :blk @as(?c.LLVMValueRef, null); - } - // Payload capture for .some - if (arm.capture) |cap_name| { - const payload = self.optionalPayload(opt_val, opt_ty); - const child_name = opt_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse unreachable; - const payload_llvm_ty = self.typeToLLVM(child_ty); - const cap_alloca = c.LLVMBuildAlloca(self.builder, payload_llvm_ty, @ptrCast(cap_name.ptr)); - _ = c.LLVMBuildStore(self.builder, payload, cap_alloca); - try self.named_values.put(cap_name, .{ .ptr = cap_alloca, .ty = child_ty }); - } - const val = try self.genExpr(arm.body); - break :blk val; - } else if (else_arm) |arm| { - const val = try self.genExpr(arm.body); - break :blk val; - } else { - self.br(merge_bb); - break :blk @as(?c.LLVMValueRef, null); - } - }; - // Record .some arm result (before branching, capture current BB) - if (some_val != null and c.LLVMGetTypeKind(c.LLVMTypeOf(some_val.?)) != c.LLVMVoidTypeKind) { - has_result = true; - if (result_type == null) result_type = c.LLVMTypeOf(some_val.?); - } - const some_out_bb = self.getCurrentBlock(); - if (some_val != null or (some_arm != null and !some_arm.?.is_break) or else_arm != null) { - // Only br if we didn't already (break case already branched) - if (some_arm == null or !some_arm.?.is_break) { - self.br(merge_bb); - } - } - - // Generate .none arm - self.positionAt(none_bb); - const none_val = blk: { - if (none_arm) |arm| { - if (arm.is_break) { - self.br(merge_bb); - break :blk @as(?c.LLVMValueRef, null); - } - const val = try self.genExpr(arm.body); - break :blk val; - } else if (else_arm) |arm| { - const val = try self.genExpr(arm.body); - break :blk val; - } else { - self.br(merge_bb); - break :blk @as(?c.LLVMValueRef, null); - } - }; - if (none_val != null and c.LLVMGetTypeKind(c.LLVMTypeOf(none_val.?)) != c.LLVMVoidTypeKind) { - has_result = true; - if (result_type == null) result_type = c.LLVMTypeOf(none_val.?); - } - const none_out_bb = self.getCurrentBlock(); - if (none_val != null or (none_arm != null and !none_arm.?.is_break) or else_arm != null) { - if (none_arm == null or !none_arm.?.is_break) { - self.br(merge_bb); - } - } - - self.positionAt(merge_bb); - - if (has_result and result_type != null) { - // Convert values to match result_type (handle int width mismatches) - const vals_to_add = [_]struct { val: ?c.LLVMValueRef, bb: c.LLVMBasicBlockRef }{ - .{ .val = some_val, .bb = some_out_bb }, - .{ .val = none_val, .bb = none_out_bb }, - }; - for (&vals_to_add) |entry| { - const v = entry.val orelse continue; - const vty = c.LLVMTypeOf(v); - if (c.LLVMGetTypeKind(vty) == c.LLVMVoidTypeKind) continue; - var converted = v; - if (vty != result_type) { - const src_kind = c.LLVMGetTypeKind(vty); - const dst_kind = c.LLVMGetTypeKind(result_type); - if (src_kind == c.LLVMIntegerTypeKind and dst_kind == c.LLVMIntegerTypeKind) { - const src_bits = c.LLVMGetIntTypeWidth(vty); - const dst_bits = c.LLVMGetIntTypeWidth(result_type); - if (src_bits > dst_bits) { - converted = self.trunc(v, result_type, "match_trunc"); - } else { - converted = c.LLVMBuildSExt(self.builder, v, result_type, "match_sext"); - } - } - } - try phi_vals.append(self.allocator, converted); - try phi_bbs.append(self.allocator, entry.bb); - } - return try self.buildPhiNode(&phi_vals, &phi_bbs, result_type, "opt_matchtmp"); - } - return null; - } - - 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; - - // Special case: optional type matching with .some/.none - if (subject_ty.isOptional()) { - return self.genOptionalMatch(match, subject_ty); - } - - // Get the switch value: for unions, load the tag from field 0; for enums, use the value directly - // For union subjects, we need a pointer for both tag loading and payload capture. - // If the subject is a simple identifier, use its existing alloca; otherwise generate - // the expression and spill into a temporary alloca. - var union_subject_ptr: c.LLVMValueRef = null; - const subject_val: c.LLVMValueRef = if (union_name != null) blk: { - const info = self.lookupTaggedEnumInfo(union_name.?).?; - if (match.subject.data == .identifier) { - const entry = self.named_values.get(match.subject.data.identifier.name).?; - union_subject_ptr = entry.ptr; - } else { - // Non-identifier subject (e.g. function call): spill to temp alloca - const val = try self.genExpr(match.subject); - const tmp = c.LLVMBuildAlloca(self.builder, info.llvm_type, "match_tmp"); - _ = c.LLVMBuildStore(self.builder, val, tmp); - union_subject_ptr = tmp; - } - break :blk self.loadStructField(info.llvm_type, union_subject_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; bool uses i1; others must match subject LLVM type - const case_int_type = if (enum_name) |en| self.getEnumLLVMType(en) else if (union_name) |un| self.getEnumLLVMType(un) else if (subject_ty == .boolean) self.i1Type() else c.LLVMTypeOf(subject_val); - 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]); - } - } else if (pat.data == .int_literal) { - const case_val = c.LLVMConstInt(case_int_type, @bitCast(@as(i64, pat.data.int_literal.value)), 0); - c.LLVMAddCase(sw, case_val, case_bbs.items[i]); - } else if (pat.data == .bool_literal) { - const case_val = c.LLVMConstInt(case_int_type, @intFromBool(pat.data.bool_literal.value), 0); - c.LLVMAddCase(sw, case_val, 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 payload_gep = self.structGEP(uinfo.llvm_type, union_subject_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; - } - } - // Struct method: obj.method(args) → StructName.method - const obj_ty_raw = self.inferType(fa.object); - const obj_ty = if (obj_ty_raw.isPointer()) - (self.resolveTypeFromName(obj_ty_raw.pointer_type.pointee_name) orelse obj_ty_raw) - else - obj_ty_raw; - if (obj_ty.isStruct()) { - const struct_name = obj_ty.struct_type; - const template_name = if (self.lookupStructInfo(struct_name)) |si| si.template_name orelse struct_name else struct_name; - const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ template_name, fa.field }) catch return null; - const qualified_z = self.allocator.dupeZ(u8, qualified) catch return null; - if (self.generic_templates.contains(qualified) or - c.LLVMGetNamedFunction(self.module, qualified_z.ptr) != null) - { - return qualified; - } - } - // Non-struct impl method: e.g., s64.eq via `impl Eq for s64` - if (!obj_ty.isStruct()) { - if (obj_ty.toName()) |tn| { - if (self.namespaces.contains(tn)) { - const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ tn, fa.field }) catch return null; - const qualified_z = self.allocator.dupeZ(u8, qualified) catch return null; - if (self.generic_templates.contains(qualified) or - c.LLVMGetNamedFunction(self.module, qualified_z.ptr) != null) - { - return qualified; - } - } - } - } - } - 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, "out")) return self.genOutCall(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, "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, "memset")) return self.genMemset(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 genOutCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { - if (args.len != 1) return self.emitError("out 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, - .optional_type_expr => |ote| std.fmt.allocPrint(self.allocator, "?{s}", .{self.typeNodeToString(ote.inner_type)}) catch "?", - .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 => {}, - }; - }, - }; - // Array display name: "[N]T" - if (name.len > 2 and name[0] == '[') { - if (std.mem.indexOfScalar(u8, name[1..], ']')) |close| { - const n_str = name[1 .. 1 + close]; - const elem = name[2 + close ..]; - const length = std.fmt.parseInt(u32, n_str, 10) catch return null; - return .{ .array_type = .{ .element_name = elem, .length = length } }; - } - } - // 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 } }; - } - } - // Closure display name: "Closure(T1, T2) -> R" or "Closure(T1, T2)" - if (name.len >= 9 and std.mem.startsWith(u8, name, "Closure(")) { - // Find matching closing paren - if (std.mem.indexOfScalar(u8, name[8..], ')')) |close_rel| { - const params_str = name[8 .. 8 + close_rel]; - const after_paren = name[8 + close_rel + 1 ..]; - - // Parse param types - var param_types_list = std.ArrayList(Type).empty; - if (params_str.len > 0) { - var start: usize = 0; - var i: usize = 0; - while (i < params_str.len) : (i += 1) { - if (i + 1 < params_str.len and params_str[i] == ',' and params_str[i + 1] == ' ') { - const pt_name = params_str[start..i]; - if (self.resolveTypeFromName(pt_name)) |pt| { - param_types_list.append(self.allocator, pt) catch {}; - } - start = i + 2; - i += 1; - } - } - // Last param - const last = params_str[start..]; - if (self.resolveTypeFromName(last)) |pt| { - param_types_list.append(self.allocator, pt) catch {}; - } - } - - // Parse return type - const ret_type = if (std.mem.startsWith(u8, after_paren, " -> ")) - self.resolveTypeFromName(after_paren[4..]) orelse Type.void_type - else - Type.void_type; - - const ret_type_ptr = self.allocator.create(Type) catch return null; - ret_type_ptr.* = ret_type; - return .{ .closure_type = .{ - .param_types = param_types_list.toOwnedSlice(self.allocator) catch return null, - .return_type = ret_type_ptr, - } }; - } - } - // 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, - .push_stmt => .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, .in_op => return .boolean, - else => { - const lhs_ty = self.inferType(binop.lhs); - const rhs_ty = self.inferType(binop.rhs); - // Tuple concatenation: (A, B) + (C, D) → (A, B, C, D) - if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) { - const li = lhs_ty.tuple_type; - const ri = rhs_ty.tuple_type; - const n = li.field_types.len + ri.field_types.len; - const ft = self.allocator.alloc(Type, n) catch return .void_type; - for (0..li.field_types.len) |i| ft[i] = li.field_types[i]; - for (0..ri.field_types.len) |i| ft[li.field_types.len + i] = ri.field_types[i]; - return .{ .tuple_type = .{ .field_types = ft, .field_names = null } }; - } - // Tuple repetition: (A, B) * 3 → (A, B, A, B, A, B) - if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) { - const li = lhs_ty.tuple_type; - const count: usize = switch (binop.rhs.data) { - .int_literal => |il| @intCast(il.value), - else => return .void_type, - }; - const n = li.field_types.len * count; - const ft = self.allocator.alloc(Type, n) catch return .void_type; - for (0..count) |r| { - for (0..li.field_types.len) |f| { - ft[r * li.field_types.len + f] = li.field_types[f]; - } - } - return .{ .tuple_type = .{ .field_types = ft, .field_names = null } }; - } - return Type.widen(lhs_ty, rhs_ty); - }, - } - }, - .chained_comparison => return .boolean, - .identifier => |ident| { - // Flow-sensitive narrowing: return narrowed type - if (self.narrowed_types.get(ident.name)) |ni| return ni.narrowed_ty; - if (self.lookupValue(ident.name)) |v| return v.ty(); - return Type.s(64); - }, - .type_expr => |te| { - // type_expr can appear when a variable name matches a type (e.g. s2, u8) - if (self.lookupValue(te.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| { - // Dot-shorthand call: .variant(payload) — type from context - if (call_node.callee.data == .enum_literal) { - if (self.current_return_type.isEnum()) return self.current_return_type; - if (self.current_return_type.isUnion()) return self.current_return_type; - if (self.current_return_type.isStruct()) return self.current_return_type; - return .{ .enum_type = "" }; - } - - // 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; - - // Struct field function pointer / closure call: obj.fn_field(args) - { - var fa_obj_ty = self.inferType(fa.object); - if (fa_obj_ty.isPointer()) { - fa_obj_ty = self.resolveTypeFromName(fa_obj_ty.pointer_type.pointee_name) orelse fa_obj_ty; - } - if (fa_obj_ty.isStruct()) { - if (self.lookupStructInfo(fa_obj_ty.struct_type)) |info| { - if (self.findNameIndex(info.field_names, fa.field)) |idx| { - const field_ty = info.field_types[idx]; - if (field_ty.isFunctionType()) { - return field_ty.function_type.return_type.*; - } - if (field_ty.isClosureType()) { - return field_ty.closure_type.return_type.*; - } - } - } - } - } - } - 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); - } - 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 .{ .pointer_type = .{ .pointee_name = "void" } }; - if (std.mem.eql(u8, base_name, "memset")) return .void_type; - // closure(lambda) → Closure(param_types) -> R - if (std.mem.eql(u8, base_name, "closure")) { - if (call_node.args.len == 1 and call_node.args[0].data == .lambda) { - const lam = call_node.args[0].data.lambda; - // If any param has inferred type, use context type directly - const has_inferred = for (lam.params) |p| { - if (p.type_expr.data == .inferred_type) break true; - } else false; - if (has_inferred) { - if (self.closure_expected_type) |ctx| { - return .{ .closure_type = ctx }; - } - return Type.s(64); // fallback — will error in genClosureIntrinsic - } - var param_types = std.ArrayList(Type).empty; - for (lam.params) |p| { - param_types.append(self.allocator, self.resolveType(p.type_expr)) catch {}; - } - const cl_ret = if (lam.return_type) |rt| self.resolveType(rt) - else if (lam.body.data == .block) Type.void_type - else self.inferType(lam.body); - const ret_ptr = self.allocator.create(Type) catch return Type.s(64); - ret_ptr.* = cl_ret; - return .{ .closure_type = .{ - .param_types = param_types.toOwnedSlice(self.allocator) catch return Type.s(64), - .return_type = ret_ptr, - } }; - } - } - // 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; - } - } - // Resolve with inferred bindings so []T, *T etc. substitute correctly - const saved_bindings = self.type_param_bindings; - self.type_param_bindings = inferred_bindings; - const resolved = self.resolveType(rt); - self.type_param_bindings = saved_bindings; - 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 or closure type - { - if (self.lookupValue(callee_name)) |v| { - if (v.ty().isFunctionType()) { - return v.ty().function_type.return_type.*; - } - if (v.ty().isClosureType()) { - return v.ty().closure_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); - }, - .null_coalesce => |nc| { - const opt_ty = self.inferType(nc.lhs); - if (opt_ty.isOptional()) { - return self.resolveTypeFromName(opt_ty.optional_type.child_name) orelse Type.s(64); - } - return Type.s(64); - }, - .force_unwrap => |fu| { - const opt_ty = self.inferType(fu.operand); - if (opt_ty.isOptional()) { - return self.resolveTypeFromName(opt_ty.optional_type.child_name) orelse Type.s(64); - } - return Type.s(64); - }, - .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| { - // Optional chaining: x?.field → ?FieldType - if (fa.is_optional) { - const opt_obj_ty = self.inferType(fa.object); - if (opt_obj_ty.isOptional()) { - const child_name = opt_obj_ty.optional_type.child_name; - const child_ty = self.resolveTypeFromName(child_name) orelse return Type.s(64); - const field_ty = self.inferFieldType(child_ty, fa.field) orelse return Type.s(64); - const dn = field_ty.displayName(self.allocator) catch return Type.s(64); - return Type{ .optional_type = .{ .child_name = dn } }; - } - } - 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 (std.mem.eql(u8, fa.field, "ptr")) return .{ .many_pointer_type = .{ .element_name = obj_ty.slice_type.element_name } }; - } - if (obj_ty.isArray()) { - if (std.mem.eql(u8, fa.field, "len")) return Type.s(64); - if (std.mem.eql(u8, fa.field, "ptr")) return .{ .many_pointer_type = .{ .element_name = obj_ty.array_type.element_name } }; - } - if (obj_ty.isClosureType()) { - if (std.mem.eql(u8, fa.field, "fn_ptr")) return .{ .pointer_type = .{ .pointee_name = "void" } }; - if (std.mem.eql(u8, fa.field, "env")) return .{ .pointer_type = .{ .pointee_name = "void" } }; - } - 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.isTuple()) { - const ti = obj_ty.tuple_type; - if (resolveTupleFieldIndex(ti, fa.field)) |idx| { - return ti.field_types[idx]; - } - } - 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]; - } - } - } - } - // Namespace constant: TypeName.CONSTANT - if (fa.object.data == .identifier) { - const ns_name = fa.object.data.identifier.name; - if (self.namespaces.contains(ns_name) or self.type_registry.contains(ns_name)) { - const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field }) catch return Type.s(64); - if (self.comptime_globals.getPtr(qualified)) |ct| { - return ct.ty; - } - } - } - 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 self.resolveTypeFromName(obj_ty.array_type.element_name) orelse Type.s(64); - } - if (obj_ty.isSlice()) { - return self.resolveTypeFromName(obj_ty.slice_type.element_name) 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) } }; - }, - .struct_literal => |sl| { - if (sl.struct_name) |sname| return .{ .struct_type = sname }; - return Type.s(64); - }, - .tuple_literal => |tl| { - const field_types = self.allocator.alloc(Type, tl.elements.len) catch return Type.s(64); - for (tl.elements, 0..) |elem, i| { - field_types[i] = self.inferType(elem.value); - } - const field_names: ?[]const []const u8 = if (tl.elements.len > 0 and tl.elements[0].name != null) blk: { - const names = self.allocator.alloc([]const u8, tl.elements.len) catch return Type.s(64); - for (tl.elements, 0..) |elem, i| { - names[i] = elem.name orelse ""; - } - break :blk names; - } else null; - return .{ .tuple_type = .{ .field_names = field_names, .field_types = field_types } }; - }, - .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(); - } - - /// Load LLVM bitcode from a file and merge it into the current module. - pub fn mergeBitcodeFile(self: *CodeGen, bc_path: []const u8) !void { - const bc_path_z = try self.allocator.dupeZ(u8, bc_path); - var buf: c.LLVMMemoryBufferRef = null; - var err_msg: [*c]u8 = null; - - if (c.LLVMCreateMemoryBufferWithContentsOfFile(bc_path_z.ptr, &buf, &err_msg) != 0) { - if (err_msg != null) { - defer c.LLVMDisposeMessage(err_msg); - const msg = std.mem.span(err_msg); - return self.emitErrorFmt("failed to read bitcode '{s}': {s}", .{ bc_path, msg }); - } - return error.CompileError; - } - - var bc_module: c.LLVMModuleRef = null; - if (c.LLVMParseBitcodeInContext2(self.context, buf, &bc_module) != 0) { - return self.emitErrorFmt("failed to parse bitcode '{s}'", .{bc_path}); - } - - // LLVMLinkModules2 destroys bc_module on success - if (c.LLVMLinkModules2(self.module, bc_module) != 0) { - return self.emitErrorFmt("failed to link bitcode module '{s}'", .{bc_path}); - } - } - - 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_str = c.LLVMPrintModuleToString(self.module); - defer c.LLVMDisposeMessage(ir_str); - const len = std.mem.len(ir_str); - std.debug.print("{s}\n", .{ir_str[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, 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, extra_objects: []const []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 }); - for (extra_objects) |eo| try argv.append(allocator, eo); - 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 }); - for (extra_objects) |eo| try argv.append(allocator, eo); - - 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; - }; -}; diff --git a/src/comptime.zig b/src/comptime.zig deleted file mode 100644 index 0de9aa7..0000000 --- a/src/comptime.zig +++ /dev/null @@ -1,2752 +0,0 @@ -const std = @import("std"); -const types = @import("types.zig"); -const Type = types.Type; -const unescape = @import("unescape.zig"); - -fn baseName(name: []const u8) []const u8 { - return if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; -} - -/// Runtime value for comptime evaluation. -/// Replaces codegen's JitResult with richer type support. -pub const Value = union(enum) { - int_val: i64, - float_val: f64, - float32_val: f32, - bool_val: bool, - string_val: []const u8, - void_val: void, - struct_val: StructValue, - array_val: ArrayValue, - type_val: Type, - function_val: FunctionVal, - pointer_val: PointerValue, - byte_ptr_val: BytePtr, - union_val: UnionValue, - any_val: AnyValue, - null_val: void, - - pub const AnyValue = struct { - tag: i64, // matches ANY_TAG_* constants from codegen - value: *Value, // the inner value (heap-allocated) - }; - - pub const PointerValue = struct { - target: [*]Value, - }; - - pub const BytePtr = struct { - data: []u8, - offset: usize, - }; - - pub const StructValue = struct { - type_name: []const u8, - field_names: []const []const u8, - fields: []Value, - }; - - pub const ArrayValue = struct { - elements: []Value, - }; - - pub const UnionValue = struct { - type_name: []const u8, - words: []Value, - }; - - pub const FunctionVal = struct { - name: []const u8, - param_count: u8, - }; - - pub fn isInt(self: Value) bool { - return self == .int_val; - } - - pub fn isFloat(self: Value) bool { - return switch (self) { - .float_val, .float32_val => true, - else => false, - }; - } - - pub fn asInt(self: Value) ?i64 { - return switch (self) { - .int_val => |v| v, - .bool_val => |v| if (v) @as(i64, 1) else 0, - else => null, - }; - } - - pub fn asIndex(self: Value) !usize { - return @intCast(self.asInt() orelse return error.TypeError); - } - - pub fn isTruthy(self: Value) bool { - return switch (self) { - .bool_val => |bv| bv, - .int_val => |iv| iv != 0, - .null_val => false, - else => true, - }; - } - - pub fn asFloat(self: Value) ?f64 { - return switch (self) { - .float_val => |v| v, - .float32_val => |v| @floatCast(v), - .int_val => |v| @floatFromInt(v), - else => null, - }; - } - - pub fn format(self: Value, allocator: std.mem.Allocator) ![]const u8 { - return switch (self) { - .int_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), - .float_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), - .float32_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), - .bool_val => |v| if (v) allocator.dupe(u8, "true") else allocator.dupe(u8, "false"), - .string_val => |v| allocator.dupe(u8, v), - .void_val => allocator.dupe(u8, "void"), - .type_val => |v| v.displayName(allocator), - .function_val => |v| std.fmt.allocPrint(allocator, "", .{v.name}), - .struct_val => |v| { - var buf: std.ArrayList(u8) = .empty; - try buf.appendSlice(allocator, v.type_name); - try buf.append(allocator, '{'); - for (v.fields, 0..) |fv, i| { - if (i > 0) try buf.appendSlice(allocator, ", "); - if (i < v.field_names.len) { - try buf.appendSlice(allocator, v.field_names[i]); - try buf.appendSlice(allocator, ": "); - } - const fs = try fv.format(allocator); - try buf.appendSlice(allocator, fs); - } - try buf.append(allocator, '}'); - return buf.items; - }, - .array_val => |v| { - var buf: std.ArrayList(u8) = .empty; - try buf.append(allocator, '['); - for (v.elements, 0..) |elem, i| { - if (i > 0) try buf.appendSlice(allocator, ", "); - const es = try elem.format(allocator); - try buf.appendSlice(allocator, es); - } - try buf.append(allocator, ']'); - return buf.items; - }, - .pointer_val => |pv| { - const inner = try pv.target[0].format(allocator); - return std.fmt.allocPrint(allocator, "@{s}", .{inner}); - }, - .byte_ptr_val => |sp| std.fmt.allocPrint(allocator, "", .{sp.offset}), - .union_val => |v| { - var buf: std.ArrayList(u8) = .empty; - try buf.appendSlice(allocator, v.type_name); - try buf.append(allocator, '{'); - for (v.words, 0..) |w, i| { - if (i > 0) try buf.appendSlice(allocator, ", "); - const ws = try w.format(allocator); - try buf.appendSlice(allocator, ws); - } - try buf.append(allocator, '}'); - return buf.items; - }, - .any_val => |v| v.value.format(allocator), - .null_val => allocator.dupe(u8, "null"), - }; - } - - /// Box a value as an Any with the appropriate tag. - pub fn boxAsAny(self: Value, allocator: std.mem.Allocator) !Value { - const tag: i64 = switch (self) { - .void_val => 0, - .bool_val => 1, - .int_val => 3, - .float32_val => 4, - .float_val => 5, - .string_val => 6, - .type_val => 10, - else => 0, - }; - const heap_val = try allocator.create(Value); - heap_val.* = self; - return .{ .any_val = .{ .tag = tag, .value = heap_val } }; - } -}; - -/// Bytecode instruction for the comptime VM. -pub const Instruction = union(enum) { - // Constants - push_int: i64, - push_float: f64, - push_f32: f32, - push_true, - push_false, - push_string: u32, // index into Chunk.strings - push_void, - push_type: Type, - push_function: FnRef, - - // Local variables - get_local: u16, // slot index in current frame - set_local: u16, - - // Global variables (resolved lazily from root_decls) - get_global: u32, // index into Chunk.strings for the global name - - // Arithmetic (type-dispatched at runtime via Value tag) - add, - sub, - mul, - div, - mod, - negate, - - // Comparison - eq, - neq, - lt, - lte, - gt, - gte, - - // Bitwise - bit_and, - bit_or, - bit_xor, - bit_not, - shl, - shr, - - // Logic - not, - - // Type conversion - cast: CastInfo, - - // Control flow - jump: i32, // relative offset - jump_if_false: i32, - jump_if_true: i32, - pop, - dup, - - // Functions - call: CallInfo, - call_builtin: BuiltinCall, - ret, - ret_void, - - // Structs - make_struct: StructMake, - get_field: u16, - set_field: u16, - - // Pointers - address_of_local: u16, // push pointer to local slot - address_of_index, // pop idx, pop array, push pointer to element - deref, // pop pointer, push dereferenced value - deref_set, // pop value, pop pointer, store through pointer - push_null, // push null pointer - - // Arrays - make_array: u32, // element count on stack - get_index, - set_index, - - // Strings - concat, - format_to_string, // convert top-of-stack value to string representation - - // Any - unwrap_any, // pop any_val, push inner value (no-op if not any_val) - - // Code insertion - eval_insert: InsertInfo, // pop string, parse as code, compile + execute inline - - // Optionals - opt_unwrap, // pop value, error if null_val, else push back - - // Unions - make_union: UnionMake, - get_union_field: UnionFieldAccess, - - pub const CastInfo = struct { to: ValueKind }; - pub const CallInfo = struct { func_name: []const u8, arg_count: u8 }; - pub const BuiltinCall = struct { id: BuiltinId, arg_count: u8 }; - pub const StructMake = struct { type_name: []const u8, field_count: u16, field_names: []const []const u8 }; - pub const FnRef = struct { name: []const u8, param_count: u8 }; - pub const InsertInfo = struct { local_names: []const []const u8 }; - pub const UnionMake = struct { type_name: []const u8, word_count: u16 }; - pub const UnionFieldAccess = struct { word_offset: u16, field_type: UnionFieldType }; -}; - -pub const UnionFieldType = enum { int, float, bool_k, pointer, string }; - -pub const ValueKind = enum { int, float, f32_k, bool_k, string }; - -pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset, type_of, alloc, dealloc }; - -/// A compiled function or expression — a flat sequence of instructions. -pub const Chunk = struct { - code: []const Instruction, - strings: []const []const u8, // string constant pool - local_count: u16, // number of local variable slots - name: []const u8, // function name (for debugging) -}; - -const ast = @import("ast.zig"); -const Node = ast.Node; -const sema = @import("sema.zig"); -const codegen_mod = @import("codegen.zig"); -const llvm = @import("llvm_api.zig"); -const Parser = @import("parser.zig").Parser; - -/// Compute byte size of a Type. Uses LLVM data layout via codegen if available, -/// otherwise falls back to known sizes for primitive types. -fn sizeOfType(ty: Type, cg: ?*codegen_mod.CodeGen) u64 { - if (cg) |gen| { - if (std.meta.eql(ty, Type.void_type)) return 0; - const llvm_ty = gen.typeToLLVM(ty); - const data_layout = llvm.c.LLVMGetModuleDataLayout(gen.module); - return llvm.c.LLVMStoreSizeOfType(data_layout, llvm_ty); - } - // Fallback without codegen - return switch (ty) { - .signed, .unsigned => |w| (w + 7) / 8, - .f32 => 4, - .f64 => 8, - .boolean => 1, - .string_type => 8, - .void_type => 0, - .enum_type => 4, - else => 0, - }; -} - -/// Compiles AST expressions into bytecode Chunks. -pub const Compiler = struct { - allocator: std.mem.Allocator, - instructions: std.ArrayList(Instruction), - strings: std.ArrayList([]const u8), - locals: std.ArrayList(Local), - scope_depth: u16, - sema_result: ?*const sema.SemaResult, - root_decls: []const *Node, - codegen: ?*codegen_mod.CodeGen, - - // Loop context for break/continue - loop_start: ?usize = null, // instruction index of condition start (for continue) - break_patches: std.ArrayList(usize) = std.ArrayList(usize).empty, // indices of break jumps to patch - - const Local = struct { - name: []const u8, - depth: u16, - type_name: ?[]const u8 = null, - }; - - pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) Compiler { - return .{ - .allocator = allocator, - .instructions = std.ArrayList(Instruction).empty, - .strings = std.ArrayList([]const u8).empty, - .locals = std.ArrayList(Local).empty, - .scope_depth = 0, - .sema_result = sema_result, - .root_decls = root_decls, - .codegen = cg, - }; - } - - pub fn compile(self: *Compiler, expr: *Node) !Chunk { - try self.compileNode(expr); - return .{ - .code = try self.instructions.toOwnedSlice(self.allocator), - .strings = try self.strings.toOwnedSlice(self.allocator), - .local_count = @intCast(self.locals.items.len), - .name = "", - }; - } - - pub fn compileFunction(self: *Compiler, fd: ast.FnDecl) !Chunk { - // Add params as locals - for (fd.params) |param| { - try self.locals.append(self.allocator, .{ .name = param.name, .depth = self.scope_depth }); - } - try self.compileNode(fd.body); - // Ensure there's a return at the end. - // If the function has a return type, emit `ret` (implicit return of last value). - // Otherwise emit `ret_void`. - const code = self.instructions.items; - if (code.len == 0 or (code[code.len - 1] != .ret and code[code.len - 1] != .ret_void)) { - const has_return_type = fd.return_type != null; - if (has_return_type) { - try self.emit(.ret); - } else { - try self.emit(.ret_void); - } - } - return .{ - .code = try self.instructions.toOwnedSlice(self.allocator), - .strings = try self.strings.toOwnedSlice(self.allocator), - .local_count = @intCast(self.locals.items.len), - .name = fd.name, - }; - } - - pub fn emit(self: *Compiler, instruction: Instruction) !void { - try self.instructions.append(self.allocator, instruction); - } - - fn patchJump(self: *Compiler, idx: usize) void { - self.instructions.items[idx] = .{ .jump = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) }; - } - - fn patchJumpIfFalse(self: *Compiler, idx: usize) void { - self.instructions.items[idx] = .{ .jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) }; - } - - fn patchJumpIfTrue(self: *Compiler, idx: usize) void { - self.instructions.items[idx] = .{ .jump_if_true = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(idx)) - 1) }; - } - - fn addString(self: *Compiler, str: []const u8) !u32 { - const idx: u32 = @intCast(self.strings.items.len); - try self.strings.append(self.allocator, str); - return idx; - } - - /// Look up a struct field index by name, handling pointer auto-deref. - /// Also resolves promoted fields from anonymous struct variants of unions. - fn resolveFieldIndex(self: *Compiler, object: *Node, field: []const u8) ?u16 { - // Check local type_name for string/slice — works even without sema - if (self.getLocalTypeName(object)) |tname| { - if (std.mem.eql(u8, tname, "string") or std.mem.startsWith(u8, tname, "[]")) { - if (std.mem.eql(u8, field, "ptr")) return 0; - if (std.mem.eql(u8, field, "len")) return 1; - return null; - } - } - if (self.sema_result) |sr| { - const obj_ty = sr.type_map.get(object) orelse { - // Sema doesn't have type info — try union fallback - return self.resolveFieldViaUnion(object, field); - }; - // String/slice field access: .ptr = 0, .len = 1 - if (obj_ty == .string_type or obj_ty.isSlice()) { - if (std.mem.eql(u8, field, "ptr")) return 0; - if (std.mem.eql(u8, field, "len")) return 1; - return null; - } - const struct_name: ?[]const u8 = if (obj_ty.isStruct()) - obj_ty.struct_type - else if (obj_ty.isPointer()) - obj_ty.pointer_type.pointee_name - else - null; - if (struct_name) |sn| { - if (sr.struct_types.get(sn)) |info| { - for (info.field_names, 0..) |fname, idx| { - if (std.mem.eql(u8, fname, field)) { - return @intCast(idx); - } - } - } - } - // Fall through to union fallback - return self.resolveFieldViaUnion(object, field); - } - return self.resolveFieldViaUnion(object, field); - } - - fn resolveFieldViaUnion(self: *Compiler, object: *Node, field: []const u8) ?u16 { - const tname = self.getLocalTypeName(object) orelse return null; - return self.resolveUnionPromotedField(tname, field); - } - - /// Find a union declaration in root_decls by name and return its word count - /// (number of 8-byte slots needed for the largest variant). - fn findUnionWordCount(self: *Compiler, type_name: []const u8) ?u16 { - for (self.root_decls) |decl| { - if (decl.data == .union_decl) { - const ud = decl.data.union_decl; - if (std.mem.eql(u8, ud.name, type_name)) { - var max_words: u16 = 0; - for (ud.field_types) |ft| { - var words: u16 = 1; // default: single-word variant - if (ft.data == .struct_decl) { - words = @intCast(ft.data.struct_decl.field_names.len); - } else if (ft.data == .type_expr) { - if (Type.fromTypeExpr(ft)) |ty| { - // string = {ptr, len} = 2 words - if (ty == .string_type) words = 2; - } - } - if (words > max_words) max_words = words; - } - return max_words; - } - } - } - return null; - } - - /// Find a union declaration in root_decls by name. - fn findUnionDecl(self: *Compiler, type_name: []const u8) ?ast.UnionDecl { - for (self.root_decls) |decl| { - if (decl.data == .union_decl) { - const ud = decl.data.union_decl; - if (std.mem.eql(u8, ud.name, type_name)) return ud; - } - } - return null; - } - - /// Resolve a promoted field from an anonymous struct variant of a union. - /// Returns the field index within the anonymous struct. - fn resolveUnionPromotedField(self: *Compiler, type_name: []const u8, field: []const u8) ?u16 { - const ud = self.findUnionDecl(type_name) orelse return null; - for (ud.field_types) |ft| { - if (ft.data == .struct_decl) { - const sd = ft.data.struct_decl; - for (sd.field_names, 0..) |fname, idx| { - if (std.mem.eql(u8, fname, field)) return @intCast(idx); - } - } - } - return null; - } - - /// Get the local's type name if the object is an identifier. - fn getLocalTypeName(self: *Compiler, object: *Node) ?[]const u8 { - if (object.data != .identifier) return null; - const slot = self.resolveLocal(object.data.identifier.name) orelse return null; - return self.locals.items[slot].type_name; - } - - /// Map a union variant's type node to a UnionFieldType for get_union_field. - fn nodeToUnionFieldType(_: *Compiler, type_node: *Node) UnionFieldType { - if (type_node.data == .type_expr) { - const name = type_node.data.type_expr.name; - if (std.mem.eql(u8, name, "string")) return .string; - if (std.mem.eql(u8, name, "s64") or std.mem.eql(u8, name, "s32") or - std.mem.eql(u8, name, "u64") or std.mem.eql(u8, name, "u32") or - std.mem.eql(u8, name, "s16") or std.mem.eql(u8, name, "u16") or - std.mem.eql(u8, name, "s8") or std.mem.eql(u8, name, "u8")) return .int; - if (std.mem.eql(u8, name, "f64") or std.mem.eql(u8, name, "f32")) return .float; - if (std.mem.eql(u8, name, "bool")) return .bool_k; - } - // Default to pointer for unknown/complex types - return .pointer; - } - - fn resolveLocal(self: *Compiler, name: []const u8) ?u16 { - var i = self.locals.items.len; - while (i > 0) { - i -= 1; - if (std.mem.eql(u8, self.locals.items[i].name, name)) { - return @intCast(i); - } - } - return null; - } - - /// Compile a string literal with escape sequences and interpolation support. - /// Handles `{expr}` patterns by parsing and compiling the inner expressions, - /// then concatenating all segments together. - /// - /// Strategy: emit each segment in order, and after each additional segment - /// (from the second one onward), emit a concat instruction to merge it with - /// the accumulated result so far. - fn compileStringLiteral(self: *Compiler, raw: []const u8) !void { - // String literals are plain text — {} is NOT interpolated here. - // String interpolation is handled by print() at the call site. - const unescaped = try unescape.unescapeString(self.allocator, raw); - const idx = try self.addString(unescaped); - try self.emit(.{ .push_string = idx }); - } - - pub fn compileNode(self: *Compiler, node: *Node) anyerror!void { - switch (node.data) { - .int_literal => |lit| { - try self.emit(.{ .push_int = lit.value }); - }, - .float_literal => |lit| { - try self.emit(.{ .push_float = lit.value }); - }, - .bool_literal => |lit| { - try self.emit(if (lit.value) .push_true else .push_false); - }, - .string_literal => |lit| { - try self.compileStringLiteral(lit.raw); - }, - .identifier => |ident| { - if (self.resolveLocal(ident.name)) |slot| { - try self.emit(.{ .get_local = slot }); - } else { - // Not a local — emit get_global to resolve lazily at runtime - const idx = try self.addString(ident.name); - try self.emit(.{ .get_global = idx }); - } - }, - .binary_op => |binop| { - if (binop.op == .and_op) { - // Short-circuit AND: LHS, dup, jump_if_false +N, pop, RHS - try self.compileNode(binop.lhs); - try self.emit(.dup); - const jump_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); - try self.emit(.pop); - try self.compileNode(binop.rhs); - self.patchJumpIfFalse(jump_idx); - } else if (binop.op == .or_op) { - // Short-circuit OR: LHS, dup, jump_if_true +N, pop, RHS - try self.compileNode(binop.lhs); - try self.emit(.dup); - const jump_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_true = 0 }); - try self.emit(.pop); - try self.compileNode(binop.rhs); - self.patchJumpIfTrue(jump_idx); - } else { - try self.compileNode(binop.lhs); - try self.compileNode(binop.rhs); - try self.emit(switch (binop.op) { - .add => .add, - .sub => .sub, - .mul => .mul, - .div => .div, - .mod => .mod, - .eq => .eq, - .neq => .neq, - .lt => .lt, - .lte => .lte, - .gt => .gt, - .gte => .gte, - .bit_and => .bit_and, - .bit_or => .bit_or, - .bit_xor => .bit_xor, - .shl => .shl, - .shr => .shr, - .and_op, .or_op, .in_op => unreachable, - }); - } - }, - .chained_comparison => |chain| { - // Compile first pair - try self.compileNode(chain.operands[0]); - try self.compileNode(chain.operands[1]); - try self.emit(switch (chain.ops[0]) { - .lt => .lt, - .lte => .lte, - .gt => .gt, - .gte => .gte, - .eq => .eq, - .neq => .neq, - else => unreachable, - }); - // For each subsequent pair, short-circuit AND - var i: usize = 1; - while (i < chain.ops.len) : (i += 1) { - try self.emit(.dup); - const jump_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); - try self.emit(.pop); - try self.compileNode(chain.operands[i]); - try self.compileNode(chain.operands[i + 1]); - try self.emit(switch (chain.ops[i]) { - .lt => .lt, - .lte => .lte, - .gt => .gt, - .gte => .gte, - .eq => .eq, - .neq => .neq, - else => unreachable, - }); - self.patchJumpIfFalse(jump_idx); - } - }, - .unary_op => |unop| { - if (unop.op == .address_of) { - if (unop.operand.data == .identifier) { - if (self.resolveLocal(unop.operand.data.identifier.name)) |slot| { - try self.emit(.{ .address_of_local = slot }); - } else { - return error.UnsupportedExpression; - } - } else if (unop.operand.data == .index_expr) { - // &arr[i] — push array, push index, address_of_index - try self.compileNode(unop.operand.data.index_expr.object); - try self.compileNode(unop.operand.data.index_expr.index); - try self.emit(.address_of_index); - } else { - return error.UnsupportedExpression; - } - } else { - try self.compileNode(unop.operand); - switch (unop.op) { - .negate => try self.emit(.negate), - .not => try self.emit(.not), - .bit_not => try self.emit(.bit_not), - .xx => try self.emit(.unwrap_any), // autocast — unwraps any_val to inner value - .address_of => unreachable, // handled above - } - } - }, - .comptime_expr => |ct| { - try self.compileNode(ct.expr); - }, - .block => |blk| { - self.scope_depth += 1; - const scope_start = self.locals.items.len; - for (blk.stmts) |stmt| { - try self.compileNode(stmt); - } - // Pop locals from this scope - while (self.locals.items.len > scope_start) { - _ = self.locals.pop(); - } - self.scope_depth -= 1; - }, - .var_decl => |vd| { - // Extract type name from annotation - const type_name: ?[]const u8 = if (vd.type_annotation) |ta| - (if (ta.data == .type_expr) ta.data.type_expr.name else null) - else - null; - - if (vd.value) |val| { - if (val.data == .undef_literal) { - // Undefined init — check if type is a union and emit make_union - if (type_name) |tname| { - if (self.findUnionWordCount(tname)) |wc| { - try self.emit(.{ .make_union = .{ .type_name = tname, .word_count = wc } }); - } else if (std.mem.eql(u8, tname, "string") or std.mem.startsWith(u8, tname, "[]")) { - // String/slice fat pointer: 2 words (ptr, len) - try self.emit(.{ .make_union = .{ .type_name = tname, .word_count = 2 } }); - } else { - try self.emit(.push_void); - } - } else { - try self.emit(.push_void); - } - } else { - try self.compileNode(val); - } - } else { - try self.emit(.push_void); - } - const slot: u16 = @intCast(self.locals.items.len); - try self.locals.append(self.allocator, .{ .name = vd.name, .depth = self.scope_depth, .type_name = type_name }); - try self.emit(.{ .set_local = slot }); - }, - .const_decl => |cd| { - try self.compileNode(cd.value); - const slot: u16 = @intCast(self.locals.items.len); - try self.locals.append(self.allocator, .{ .name = cd.name, .depth = self.scope_depth }); - try self.emit(.{ .set_local = slot }); - }, - .assignment => |asgn| { - if (asgn.target.data == .identifier) { - if (self.resolveLocal(asgn.target.data.identifier.name)) |slot| { - if (asgn.op != .assign) { - // Compound assignment: get current value, compile RHS, apply op, set - try self.emit(.{ .get_local = slot }); - try self.compileNode(asgn.value); - try self.emit(switch (asgn.op) { - .add_assign => .add, - .sub_assign => .sub, - .mul_assign => .mul, - .div_assign => .div, - .mod_assign => .mod, - .and_assign => .bit_and, - .or_assign => .bit_or, - .xor_assign => .bit_xor, - .shl_assign => .shl, - .shr_assign => .shr, - .assign => unreachable, - }); - } else { - try self.compileNode(asgn.value); - } - try self.emit(.{ .set_local = slot }); - } else { - return error.UndefinedVariable; - } - } else if (asgn.target.data == .index_expr) { - // arr[i] = val → push arr, push idx, push val, set_index - const ie = asgn.target.data.index_expr; - try self.compileNode(ie.object); - try self.compileNode(ie.index); - if (asgn.op != .assign) { - // Compound: get current, apply op with RHS - try self.emit(.dup); // dup index - // We need the array and index for both get and set - // Stack: arr, idx — but we need arr[idx] for the compound op - // Simpler: just support simple assign for index targets - return error.UnsupportedExpression; - } - try self.compileNode(asgn.value); - try self.emit(.set_index); - // set_index pushes the modified container back; store it back if it's a local - if (ie.object.data == .identifier) { - if (self.resolveLocal(ie.object.data.identifier.name)) |slot| { - try self.emit(.{ .set_local = slot }); - } - } - } else if (asgn.target.data == .field_access) { - // obj.field = val (works with auto-deref for pointers) - const fa = asgn.target.data.field_access; - const field_idx = self.resolveFieldIndex(fa.object, fa.field) orelse return error.UnsupportedExpression; - try self.compileNode(fa.object); - if (asgn.op != .assign) return error.UnsupportedExpression; - try self.compileNode(asgn.value); - try self.emit(.{ .set_field = field_idx }); - // Store back to local - if (fa.object.data == .identifier) { - if (self.resolveLocal(fa.object.data.identifier.name)) |slot| { - try self.emit(.{ .set_local = slot }); - } - } - } else if (asgn.target.data == .deref_expr) { - // p.* = val - try self.compileNode(asgn.target.data.deref_expr.operand); - if (asgn.op != .assign) return error.UnsupportedExpression; - try self.compileNode(asgn.value); - try self.emit(.deref_set); - } - }, - .return_stmt => |rs| { - if (rs.value) |val| { - try self.compileNode(val); - try self.emit(.ret); - } else { - try self.emit(.ret_void); - } - }, - .if_expr => |ie| { - if (ie.binding_name) |binding_name| { - // if val := optional_expr { ... } else { ... } - try self.compileNode(ie.condition); - // Dup the optional value, test truthiness - try self.emit(.dup); - const jump_false_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); // placeholder - // Non-null path: the value is on the stack, bind as local - const slot: u16 = @intCast(self.locals.items.len); - try self.locals.append(self.allocator, .{ .name = binding_name, .depth = self.scope_depth }); - try self.emit(.{ .set_local = slot }); - try self.compileNode(ie.then_branch); - if (ie.else_branch) |eb| { - const jump_end_idx = self.instructions.items.len; - try self.emit(.{ .jump = 0 }); // placeholder - self.patchJumpIfFalse(jump_false_idx); - try self.emit(.pop); // discard the null value - try self.compileNode(eb); - self.patchJump(jump_end_idx); - } else { - self.patchJumpIfFalse(jump_false_idx); - try self.emit(.pop); // discard the null value - } - } else { - try self.compileNode(ie.condition); - const jump_false_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); // placeholder - try self.compileNode(ie.then_branch); - if (ie.else_branch) |eb| { - const jump_end_idx = self.instructions.items.len; - try self.emit(.{ .jump = 0 }); // placeholder - self.patchJumpIfFalse(jump_false_idx); - try self.compileNode(eb); - self.patchJump(jump_end_idx); - } else { - self.patchJumpIfFalse(jump_false_idx); - } - } - }, - .call => |call_node| { - // Compile arguments - for (call_node.args) |arg| { - try self.compileNode(arg); - } - // Resolve callee name - const callee_name = if (call_node.callee.data == .identifier) - call_node.callee.data.identifier.name - else if (call_node.callee.data == .field_access) blk: { - // Use the innermost field name as the callee (covers UFCS and namespaced calls). - // For struct field fn-ptr calls (e.g. context.allocator.alloc_fn), this won't - // resolve to a real function, but the call instruction is only fatal at execution - // time — dead branches (like comptime fallback to malloc) never execute it. - break :blk call_node.callee.data.field_access.field; - } else null; - - if (callee_name) |name| { - // Check if it's a builtin - const base = baseName(name); - if (std.meta.stringToEnum(BuiltinId, base)) |id| { - try self.emit(.{ .call_builtin = .{ .id = id, .arg_count = @intCast(call_node.args.len) } }); - } else { - try self.emit(.{ .call = .{ .func_name = name, .arg_count = @intCast(call_node.args.len) } }); - } - } else { - return error.InvalidCallee; - } - }, - .match_expr => |me| { - try self.compileNode(me.subject); - var end_jumps = std.ArrayList(usize).empty; - for (me.arms) |arm| { - if (arm.pattern) |pattern| { - try self.emit(.dup); // duplicate subject for comparison - try self.compileNode(pattern); - try self.emit(.eq); - const jump_next_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); // placeholder - try self.emit(.pop); // pop the subject copy - try self.compileNode(arm.body); - try end_jumps.append(self.allocator, self.instructions.items.len); - try self.emit(.{ .jump = 0 }); // placeholder jump to end - self.patchJumpIfFalse(jump_next_idx); - } else { - // else arm: unconditionally execute body - try self.emit(.pop); // pop the subject copy - try self.compileNode(arm.body); - try end_jumps.append(self.allocator, self.instructions.items.len); - try self.emit(.{ .jump = 0 }); // placeholder jump to end - } - } - try self.emit(.pop); // pop remaining subject - // Patch all end jumps - for (end_jumps.items) |idx| { - self.patchJump(idx); - } - }, - .struct_literal => |sl| { - for (sl.field_inits) |fi| { - try self.compileNode(fi.value); - } - const name = sl.struct_name orelse ""; - const fnames = try self.allocator.alloc([]const u8, sl.field_inits.len); - for (sl.field_inits, 0..) |fi, i| { - fnames[i] = fi.name orelse ""; - } - try self.emit(.{ .make_struct = .{ .type_name = name, .field_count = @intCast(sl.field_inits.len), .field_names = fnames } }); - }, - .field_access => |fa| { - try self.compileNode(fa.object); - // Check for string field access (.len, .ptr) - if (self.sema_result) |sr| { - const obj_ty = sr.type_map.get(fa.object); - if (obj_ty != null and obj_ty.? == .string_type) { - if (std.mem.eql(u8, fa.field, "len")) { - try self.emit(.{ .get_field = 1 }); // len is field 1 in {ptr, len} - return; - } else if (std.mem.eql(u8, fa.field, "ptr")) { - try self.emit(.{ .get_field = 0 }); // ptr is field 0 - return; - } - } - } - // Look up field index from sema struct_types (handles pointer auto-deref) - if (self.resolveFieldIndex(fa.object, fa.field)) |field_idx| { - try self.emit(.{ .get_field = field_idx }); - return; - } - // Check for union variant access (e.g. rs.s where rs is CString union) - if (self.getLocalTypeName(fa.object)) |tname| { - if (self.findUnionDecl(tname)) |ud| { - for (ud.field_names, ud.field_types) |fname, ftype| { - if (std.mem.eql(u8, fname, fa.field)) { - const uft = self.nodeToUnionFieldType(ftype); - try self.emit(.{ .get_union_field = .{ .word_offset = 0, .field_type = uft } }); - return; - } - } - } - } - // Fallback for untyped field access (e.g. imported function bodies - // without sema info): assume fat pointer layout {ptr=0, len=1} - if (std.mem.eql(u8, fa.field, "len")) { - try self.emit(.{ .get_field = 1 }); - } else if (std.mem.eql(u8, fa.field, "ptr")) { - try self.emit(.{ .get_field = 0 }); - } else { - // Other fields (e.g. union promoted fields) default to field 0 - try self.emit(.{ .get_field = 0 }); - } - }, - .array_literal => |al| { - for (al.elements) |elem| { - try self.compileNode(elem); - } - try self.emit(.{ .make_array = @intCast(al.elements.len) }); - }, - .index_expr => |ie| { - try self.compileNode(ie.object); - try self.compileNode(ie.index); - try self.emit(.get_index); - }, - .type_expr => |te| { - const resolved = if (self.sema_result) |sr| - sr.type_map.get(node) orelse Type.fromName(te.name) orelse .void_type - else - Type.fromName(te.name) orelse .void_type; - try self.emit(.{ .push_type = resolved }); - }, - .enum_literal => |el| { - const idx = try self.addString(el.name); - try self.emit(.{ .push_string = idx }); - }, - .while_expr => |we| { - // Save outer loop context - const saved_loop_start = self.loop_start; - const saved_break_patches = self.break_patches; - self.break_patches = std.ArrayList(usize).empty; - - // Record condition start position - const condition_start = self.instructions.items.len; - self.loop_start = condition_start; - - // Compile condition - try self.compileNode(we.condition); - - // Jump past body if false - const jump_false_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_false = 0 }); // placeholder - - // Compile body - try self.compileNode(we.body); - - // Jump back to condition - const back_offset = @as(i32, @intCast(condition_start)) - @as(i32, @intCast(self.instructions.items.len)) - 1; - try self.emit(.{ .jump = back_offset }); - - // Patch jump_if_false to after the loop - self.patchJumpIfFalse(jump_false_idx); - - // Patch all break jumps to after the loop - for (self.break_patches.items) |patch_idx| { - self.patchJump(patch_idx); - } - - // Restore outer loop context - self.loop_start = saved_loop_start; - self.break_patches = saved_break_patches; - }, - .break_expr => { - // Emit placeholder jump, record for patching - try self.break_patches.append(self.allocator, self.instructions.items.len); - try self.emit(.{ .jump = 0 }); // placeholder — patched when while ends - }, - .continue_expr => { - // Jump back to condition start - const target = self.loop_start orelse return error.UnsupportedExpression; - const offset = @as(i32, @intCast(target)) - @as(i32, @intCast(self.instructions.items.len)) - 1; - try self.emit(.{ .jump = offset }); - }, - .deref_expr => |de| { - try self.compileNode(de.operand); - try self.emit(.deref); - }, - .null_literal => { - try self.emit(.push_null); - }, - .pointer_type_expr, .many_pointer_type_expr, .tuple_type_expr => { - try self.emit(.push_void); // type expressions not meaningful as values - }, - .undef_literal => { - try self.emit(.push_void); - }, - .defer_stmt => {}, // defer not meaningful in comptime - .push_stmt => {}, // push not meaningful in comptime - .insert_expr => |ins| { - // Compile the inner expression (evaluates to a string at runtime). - // Then emit eval_insert which at VM execution time will: - // 1. Pop the string result - // 2. Parse it as code - // 3. Compile a sub-chunk (with current locals) - // 4. Execute inline in the current frame - try self.compileNode(ins.expr); - // Snapshot current local names so the VM can set up the sub-compiler - var names = std.ArrayList([]const u8).empty; - for (self.locals.items) |local| { - try names.append(self.allocator, local.name); - } - try self.emit(.{ .eval_insert = .{ - .local_names = try names.toOwnedSlice(self.allocator), - } }); - }, - .tuple_literal => |tl| { - for (tl.elements) |elem| { - try self.compileNode(elem.value); - } - const fnames = try self.allocator.alloc([]const u8, tl.elements.len); - for (tl.elements, 0..) |elem, i| { - fnames[i] = elem.name orelse ""; - } - try self.emit(.{ .make_struct = .{ - .type_name = "__tuple", - .field_count = @intCast(tl.elements.len), - .field_names = fnames, - } }); - }, - .force_unwrap => |fu| { - try self.compileNode(fu.operand); - try self.emit(.opt_unwrap); - }, - .null_coalesce => |nc| { - // x ?? y: evaluate x, if non-null keep it, else evaluate y - try self.compileNode(nc.lhs); - try self.emit(.dup); - const jump_idx = self.instructions.items.len; - try self.emit(.{ .jump_if_true = 0 }); // placeholder - try self.emit(.pop); // discard the null - try self.compileNode(nc.rhs); - self.patchJumpIfTrue(jump_idx); - }, - .ufcs_alias => {}, // UFCS aliases are resolved at codegen, no-op in comptime - else => { - return error.UnsupportedExpression; - }, - } - } -}; - -/// Stack-based virtual machine for comptime bytecode execution. -pub const VM = struct { - stack: [256]Value = undefined, - sp: u16 = 0, - frames: [64]CallFrame = undefined, - fp: u8 = 0, - insert_stack: [16]InsertSave = undefined, - insert_sp: u8 = 0, - insert_locals: std.ArrayList(Compiler.Local) = std.ArrayList(Compiler.Local).empty, - functions: std.StringHashMap(*Chunk), - globals: std.StringHashMap(Value), - allocator: std.mem.Allocator, - sema_result: ?*const sema.SemaResult, - root_decls: []const *Node, - codegen: ?*codegen_mod.CodeGen, - - pub const InsertSave = struct { - chunk: *const Chunk, - ip: u32, - }; - - pub const CallFrame = struct { - chunk: *const Chunk, - ip: u32, - base_slot: u16, - }; - - pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) VM { - return .{ - .functions = std.StringHashMap(*Chunk).init(allocator), - .globals = std.StringHashMap(Value).init(allocator), - .allocator = allocator, - .sema_result = sema_result, - .root_decls = root_decls, - .codegen = cg, - }; - } - - /// Pre-initialize `context` global with null arena for comptime evaluation. - /// At comptime, cstring uses malloc (a safe comptime builtin) as fallback. - pub fn setupComptimeContext(self: *VM) !void { - // Context struct: { allocator: Allocator{ctx, alloc, free}, data: *void } - // All allocator fields are null/zero so cstring takes the malloc path at comptime - const alloc_fields = try self.allocator.alloc(Value, 3); - const alloc_field_names = try self.allocator.alloc([]const u8, 3); - alloc_field_names[0] = "ctx"; - alloc_field_names[1] = "alloc"; - alloc_field_names[2] = "free"; - alloc_fields[0] = .{ .null_val = {} }; // null ctx pointer - alloc_fields[1] = .{ .null_val = {} }; // null alloc - alloc_fields[2] = .{ .null_val = {} }; // null free - - const ctx_fields = try self.allocator.alloc(Value, 2); - const ctx_field_names = try self.allocator.alloc([]const u8, 2); - ctx_field_names[0] = "allocator"; - ctx_field_names[1] = "data"; - ctx_fields[0] = .{ .struct_val = .{ - .type_name = "Allocator", - .field_names = alloc_field_names, - .fields = alloc_fields, - } }; - ctx_fields[1] = .{ .null_val = {} }; - - try self.globals.put("context", .{ .struct_val = .{ - .type_name = "Context", - .field_names = ctx_field_names, - .fields = ctx_fields, - } }); - } - - pub fn push(self: *VM, value: Value) !void { - if (self.sp >= 256) return error.StackOverflow; - self.stack[self.sp] = value; - self.sp += 1; - } - - fn pop(self: *VM) !Value { - if (self.sp == 0) return error.StackUnderflow; - self.sp -= 1; - return self.stack[self.sp]; - } - - fn peek(self: *VM) !Value { - if (self.sp == 0) return error.StackUnderflow; - return self.stack[self.sp - 1]; - } - - pub fn execute(self: *VM, chunk: *const Chunk) !Value { - // Set up initial frame - self.frames[0] = .{ .chunk = chunk, .ip = 0, .base_slot = 0 }; - self.fp = 1; - - return self.run(); - } - - pub fn run(self: *VM) !Value { - while (true) { - const frame = &self.frames[self.fp - 1]; - if (frame.ip >= frame.chunk.code.len) { - // If we're inside an #insert, restore parent chunk and continue - if (self.insert_sp > 0) { - self.insert_sp -= 1; - const saved = self.insert_stack[self.insert_sp]; - frame.chunk = saved.chunk; - frame.ip = saved.ip; - continue; - } - // End of chunk — return top of stack or void - if (self.sp > frame.base_slot) { - return self.pop(); - } - return .{ .void_val = {} }; - } - - const instruction = frame.chunk.code[frame.ip]; - frame.ip += 1; - - switch (instruction) { - // Constants - .push_int => |v| try self.push(.{ .int_val = v }), - .push_float => |v| try self.push(.{ .float_val = v }), - .push_f32 => |v| try self.push(.{ .float32_val = v }), - .push_true => try self.push(.{ .bool_val = true }), - .push_false => try self.push(.{ .bool_val = false }), - .push_string => |idx| { - if (idx < frame.chunk.strings.len) { - try self.push(.{ .string_val = frame.chunk.strings[idx] }); - } else { - try self.push(.{ .string_val = "" }); - } - }, - .push_void => try self.push(.{ .void_val = {} }), - .push_type => |t| try self.push(.{ .type_val = t }), - .push_function => |fr| try self.push(.{ .function_val = .{ .name = fr.name, .param_count = fr.param_count } }), - - // Stack ops - .pop => _ = try self.pop(), - .dup => { - const v = try self.peek(); - try self.push(v); - }, - - // Local variables - .get_local => |slot| { - const abs_slot = frame.base_slot + slot; - if (abs_slot < self.sp) { - try self.push(self.stack[abs_slot]); - } else { - try self.push(.{ .void_val = {} }); - } - }, - .set_local => |slot| { - const abs_slot = frame.base_slot + slot; - const val = try self.pop(); - // Grow stack if needed - while (self.sp <= abs_slot) { - self.stack[self.sp] = .{ .void_val = {} }; - self.sp += 1; - } - self.stack[abs_slot] = val; - }, - - // Pointers - .address_of_local => |slot| { - const abs_slot = frame.base_slot + slot; - // Grow stack if needed so the target slot exists - while (self.sp <= abs_slot) { - self.stack[self.sp] = .{ .void_val = {} }; - self.sp += 1; - } - const ptr: [*]Value = @ptrCast(&self.stack[abs_slot]); - try self.push(.{ .pointer_val = .{ .target = ptr } }); - }, - .address_of_index => { - const idx_val = try self.pop(); - const arr = try self.pop(); - const idx: usize = try idx_val.asIndex(); - if (arr == .array_val) { - if (idx >= arr.array_val.elements.len) return error.IndexOutOfBounds; - try self.push(.{ .pointer_val = .{ .target = arr.array_val.elements.ptr + idx } }); - } else if (arr == .string_val) { - if (idx > arr.string_val.len) return error.IndexOutOfBounds; - try self.push(.{ .byte_ptr_val = .{ .data = @constCast(arr.string_val), .offset = idx } }); - } else { - return error.TypeError; - } - }, - .deref => { - const v = try self.pop(); - if (v == .pointer_val) { - try self.push(try self.cloneValue(v.pointer_val.target[0])); - } else if (v == .null_val) { - return error.NullDereference; - } else { - return error.TypeError; - } - }, - .deref_set => { - const val = try self.pop(); - const ptr_v = try self.pop(); - if (ptr_v == .pointer_val) { - ptr_v.pointer_val.target[0] = val; - } else if (ptr_v == .null_val) { - return error.NullDereference; - } else { - return error.TypeError; - } - }, - .push_null => try self.push(.{ .null_val = {} }), - - // Global variables (lazily resolved from root_decls) - .get_global => |name_idx| { - const name = if (name_idx < frame.chunk.strings.len) frame.chunk.strings[name_idx] else return error.InvalidGlobal; - try self.push(try self.resolveGlobal(name)); - }, - - // Arithmetic - .add => try self.execArith(.add_op), - .sub => try self.execArith(.sub_op), - .mul => try self.execArith(.mul_op), - .div => try self.execArith(.div_op), - .mod => try self.execArith(.mod_op), - .bit_and => { - const b = try self.pop(); - const a = try self.pop(); - if (a == .int_val and b == .int_val) { - try self.push(.{ .int_val = a.int_val & b.int_val }); - } else return error.TypeError; - }, - .bit_or => { - const b = try self.pop(); - const a = try self.pop(); - if (a == .int_val and b == .int_val) { - try self.push(.{ .int_val = a.int_val | b.int_val }); - } else return error.TypeError; - }, - .bit_xor => { - const b = try self.pop(); - const a = try self.pop(); - if (a == .int_val and b == .int_val) { - try self.push(.{ .int_val = a.int_val ^ b.int_val }); - } else return error.TypeError; - }, - .shl => { - const b = try self.pop(); - const a = try self.pop(); - if (a == .int_val and b == .int_val) { - const shift: u6 = @intCast(@as(u64, @bitCast(b.int_val)) & 63); - try self.push(.{ .int_val = a.int_val << shift }); - } else return error.TypeError; - }, - .shr => { - const b = try self.pop(); - const a = try self.pop(); - if (a == .int_val and b == .int_val) { - const shift: u6 = @intCast(@as(u64, @bitCast(b.int_val)) & 63); - try self.push(.{ .int_val = a.int_val >> shift }); - } else return error.TypeError; - }, - .bit_not => { - const v = try self.pop(); - if (v == .int_val) { - try self.push(.{ .int_val = ~v.int_val }); - } else return error.TypeError; - }, - .negate => { - const v = try self.pop(); - try self.push(switch (v) { - .int_val => |i| Value{ .int_val = -i }, - .float_val => |f| Value{ .float_val = -f }, - .float32_val => |f| Value{ .float32_val = -f }, - else => return error.TypeError, - }); - }, - - // Comparison - .eq => try self.execComparison(.eq), - .neq => try self.execComparison(.neq), - .lt => try self.execComparison(.lt), - .lte => try self.execComparison(.lte), - .gt => try self.execComparison(.gt), - .gte => try self.execComparison(.gte), - .not => { - const v = try self.pop(); - try self.push(.{ .bool_val = !v.isTruthy() }); - }, - - // Control flow - .jump => |offset| { - frame.ip = @intCast(@as(i64, frame.ip) + offset); - }, - .jump_if_false => |offset| { - const v = try self.pop(); - if (!v.isTruthy()) { - frame.ip = @intCast(@as(i64, frame.ip) + offset); - } - }, - .jump_if_true => |offset| { - const v = try self.pop(); - if (v.isTruthy()) { - frame.ip = @intCast(@as(i64, frame.ip) + offset); - } - }, - - // Functions - .call => |ci| { - try self.callFunction(ci.func_name, ci.arg_count); - }, - .call_builtin => |bi| { - try self.callBuiltin(bi.id, bi.arg_count); - }, - .ret => { - const result = try self.pop(); - if (self.fp <= 1) return result; - // Pop frame - self.fp -= 1; - self.sp = frame.base_slot; - try self.push(result); - }, - .ret_void => { - if (self.fp <= 1) return .{ .void_val = {} }; - self.fp -= 1; - self.sp = frame.base_slot; - try self.push(.{ .void_val = {} }); - }, - - // Structs - .make_struct => |sm| { - const fields = try self.allocator.alloc(Value, sm.field_count); - var i: u16 = sm.field_count; - while (i > 0) { - i -= 1; - fields[i] = try self.pop(); - } - try self.push(.{ .struct_val = .{ .type_name = sm.type_name, .field_names = sm.field_names, .fields = fields } }); - }, - .get_field => |idx| { - const raw_obj = try self.pop(); - // Auto-deref pointer - const obj = if (raw_obj == .pointer_val) raw_obj.pointer_val.target[0] else raw_obj; - if (obj == .struct_val) { - if (idx < obj.struct_val.fields.len) { - try self.push(obj.struct_val.fields[idx]); - } else { - try self.push(.{ .void_val = {} }); - } - } else if (obj == .string_val) { - // String slice: field 0 = ptr (byte-level pointer), field 1 = len - if (idx == 1) { - try self.push(.{ .int_val = @intCast(obj.string_val.len) }); - } else { - try self.push(.{ .byte_ptr_val = .{ .data = @constCast(obj.string_val), .offset = 0 } }); - } - } else if (obj == .union_val) { - if (idx < obj.union_val.words.len) { - try self.push(obj.union_val.words[idx]); - } else { - try self.push(.{ .void_val = {} }); - } - } else if (obj == .void_val) { - // Undefined/zeroed struct — all fields are zero - try self.push(.{ .int_val = 0 }); - } else { - return error.TypeError; - } - }, - .set_field => |idx| { - const val = try self.pop(); - const raw_obj = try self.pop(); - if (raw_obj == .pointer_val) { - // Auto-deref: mutate field in-place through pointer - const target = raw_obj.pointer_val.target; - if (target[0] == .struct_val) { - const sv = target[0].struct_val; - if (idx < sv.fields.len) { - sv.fields[idx] = val; - } - } else if (target[0] == .union_val) { - const uv = target[0].union_val; - if (idx < uv.words.len) { - uv.words[idx] = val; - } - } - try self.push(raw_obj); // push pointer back - } else if (raw_obj == .struct_val) { - if (idx < raw_obj.struct_val.fields.len) { - raw_obj.struct_val.fields[idx] = val; - } - try self.push(raw_obj); - } else if (raw_obj == .union_val) { - if (idx < raw_obj.union_val.words.len) { - raw_obj.union_val.words[idx] = val; - } - // Auto-finalize: string/slice fat pointer → string_val - const words = raw_obj.union_val.words; - const tn = raw_obj.union_val.type_name; - if (words.len == 2 and words[0] == .byte_ptr_val and words[1] == .int_val and - (std.mem.eql(u8, tn, "string") or std.mem.startsWith(u8, tn, "[]"))) - { - const bp = words[0].byte_ptr_val; - const len: usize = @intCast(@max(0, words[1].int_val)); - try self.push(.{ .string_val = bp.data[bp.offset .. bp.offset + len] }); - } else { - try self.push(raw_obj); - } - } else { - return error.TypeError; - } - }, - - // Arrays - .make_array => |count| { - const elements = try self.allocator.alloc(Value, count); - var i: u32 = count; - while (i > 0) { - i -= 1; - elements[i] = try self.pop(); - } - try self.push(.{ .array_val = .{ .elements = elements } }); - }, - .get_index => { - const idx_val = try self.pop(); - const arr = try self.pop(); - if (arr == .array_val) { - const idx: usize = try idx_val.asIndex(); - if (idx < arr.array_val.elements.len) { - try self.push(arr.array_val.elements[idx]); - } else { - return error.IndexOutOfBounds; - } - } else if (arr == .string_val) { - // String indexing: return byte as int - const idx: usize = try idx_val.asIndex(); - if (idx < arr.string_val.len) { - try self.push(.{ .int_val = @intCast(arr.string_val[idx]) }); - } else { - return error.IndexOutOfBounds; - } - } else if (arr == .pointer_val) { - // Many-pointer indexing: ptr[i] - const idx: usize = try idx_val.asIndex(); - try self.push(arr.pointer_val.target[idx]); - } else { - return error.TypeError; - } - }, - .set_index => { - const val = try self.pop(); - const idx_val = try self.pop(); - const arr = try self.pop(); - if (arr == .array_val) { - const idx: usize = try idx_val.asIndex(); - if (idx < arr.array_val.elements.len) { - arr.array_val.elements[idx] = val; - } - try self.push(arr); - } else if (arr == .string_val) { - // String index assignment: mutate byte - const idx: usize = try idx_val.asIndex(); - const byte_val: u8 = @intCast(val.asInt() orelse return error.TypeError); - if (idx < arr.string_val.len) { - const mutable = @constCast(arr.string_val); - mutable[idx] = byte_val; - } - try self.push(arr); - } else if (arr == .pointer_val) { - // Many-pointer index assignment: ptr[i] = val - const idx: usize = try idx_val.asIndex(); - arr.pointer_val.target[idx] = val; - try self.push(arr); - } else { - return error.TypeError; - } - }, - - // Strings - .concat => { - const b = try self.pop(); - const a = try self.pop(); - const sa = try a.format(self.allocator); - const sb = try b.format(self.allocator); - const result = try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ sa, sb }); - try self.push(.{ .string_val = result }); - }, - .format_to_string => { - const v = try self.pop(); - const s = try v.format(self.allocator); - try self.push(.{ .string_val = s }); - }, - - // Any - .unwrap_any => { - const val = try self.pop(); - if (val == .any_val) { - try self.push(val.any_val.value.*); - } else { - try self.push(val); // pass through for non-Any values - } - }, - - .opt_unwrap => { - const val = try self.pop(); - if (val == .null_val) return error.NullDereference; - try self.push(val); - }, - - // Code insertion - .eval_insert => |info| { - // Pop the code string (result of evaluating the inner expression) - const code_val = try self.pop(); - if (code_val != .string_val) return error.CompileError; - const code_z = self.allocator.dupeZ(u8, code_val.string_val) catch return error.OutOfMemory; - - // Compile with parent's locals + any locals created by previous inserts - var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); - for (info.local_names) |name| { - compiler.locals.append(self.allocator, .{ .name = name, .depth = 0 }) catch return error.OutOfMemory; - } - for (self.insert_locals.items) |local| { - compiler.locals.append(self.allocator, local) catch return error.OutOfMemory; - } - const pre_local_count = compiler.locals.items.len; - - // Parse and compile each statement - var parser = Parser.init(self.allocator, code_z); - while (parser.current.tag != .eof) { - const stmt = parser.parseStmt() catch return error.CompileError; - compiler.compileNode(stmt) catch return error.CompileError; - } - // NO ret — sub-chunk runs inline, ends when ip >= code.len - - // Track new locals created by this insert for subsequent inserts - if (compiler.locals.items.len > pre_local_count) { - for (compiler.locals.items[pre_local_count..]) |local| { - self.insert_locals.append(self.allocator, local) catch return error.OutOfMemory; - } - } - - const sub_code = compiler.instructions.toOwnedSlice(self.allocator) catch return error.OutOfMemory; - const sub_strings = compiler.strings.toOwnedSlice(self.allocator) catch return error.OutOfMemory; - const sub_chunk = self.allocator.create(Chunk) catch return error.OutOfMemory; - sub_chunk.* = .{ - .code = sub_code, - .strings = sub_strings, - .local_count = @intCast(compiler.locals.items.len), - .name = "insert", - }; - - // Save parent chunk/ip on insert stack, swap to sub-chunk - if (self.insert_sp >= 16) return error.StackOverflow; - self.insert_stack[self.insert_sp] = .{ .chunk = frame.chunk, .ip = frame.ip }; - self.insert_sp += 1; - frame.chunk = sub_chunk; - frame.ip = 0; - continue; // re-enter the run loop, now executing sub-chunk - }, - - // Unions - .make_union => |um| { - const words = try self.allocator.alloc(Value, um.word_count); - @memset(words, .{ .void_val = {} }); - try self.push(.{ .union_val = .{ .type_name = um.type_name, .words = words } }); - }, - .get_union_field => |uf| { - const raw_obj = try self.pop(); - const obj = if (raw_obj == .pointer_val) raw_obj.pointer_val.target[0] else raw_obj; - if (obj == .union_val) { - const words = obj.union_val.words; - switch (uf.field_type) { - .string => { - // Reconstruct string_val from words[0] (byte_ptr_val) + words[1] (int_val) - if (uf.word_offset + 1 < words.len or (uf.word_offset == 0 and words.len >= 2)) { - const ptr_word = words[uf.word_offset]; - const len_word = words[uf.word_offset + 1]; - if (ptr_word == .byte_ptr_val) { - const bp = ptr_word.byte_ptr_val; - const len: usize = if (len_word.asInt()) |v| @intCast(@max(0, v)) else 0; - const end = @min(bp.offset + len, bp.data.len); - try self.push(.{ .string_val = bp.data[bp.offset..end] }); - } else { - try self.push(.{ .string_val = "" }); - } - } else { - try self.push(.{ .string_val = "" }); - } - }, - else => { - // Single-word read - if (uf.word_offset < words.len) { - try self.push(words[uf.word_offset]); - } else { - try self.push(.{ .void_val = {} }); - } - }, - } - } else { - return error.TypeError; - } - }, - - .cast => {}, - } - } - } - - const ArithOp = enum { add_op, sub_op, mul_op, div_op, mod_op }; - - fn execArith(self: *VM, op: ArithOp) !void { - const b = try self.pop(); - const a = try self.pop(); - try self.push(try self.arith(a, b, op)); - } - - const CmpOp = enum { eq, neq, lt, lte, gt, gte }; - - fn execComparison(self: *VM, comptime op: CmpOp) !void { - const b = try self.pop(); - const a = try self.pop(); - const result = switch (op) { - .eq => self.valEqual(a, b), - .neq => !self.valEqual(a, b), - .lt => self.valLess(a, b), - .lte => self.valLess(a, b) or self.valEqual(a, b), - .gt => self.valLess(b, a), - .gte => self.valLess(b, a) or self.valEqual(a, b), - }; - try self.push(.{ .bool_val = result }); - } - - fn arith(self: *VM, a: Value, b: Value, op: ArithOp) !Value { - _ = self; - // Both int - if (a == .int_val and b == .int_val) { - const ai = a.int_val; - const bi = b.int_val; - return .{ .int_val = switch (op) { - .add_op => ai + bi, - .sub_op => ai - bi, - .mul_op => ai * bi, - .div_op => if (bi != 0) @divTrunc(ai, bi) else return error.DivisionByZero, - .mod_op => if (bi != 0) @rem(ai, bi) else return error.DivisionByZero, - } }; - } - - // Both f32 - if (a == .float32_val and b == .float32_val) { - const af = a.float32_val; - const bf = b.float32_val; - return .{ .float32_val = switch (op) { - .add_op => af + bf, - .sub_op => af - bf, - .mul_op => af * bf, - .div_op => af / bf, - .mod_op => @rem(af, bf), - } }; - } - - // Promote to f64 - const af = a.asFloat() orelse return error.TypeError; - const bf = b.asFloat() orelse return error.TypeError; - return .{ .float_val = switch (op) { - .add_op => af + bf, - .sub_op => af - bf, - .mul_op => af * bf, - .div_op => af / bf, - .mod_op => @rem(af, bf), - } }; - } - - /// Clone a value, allocating new backing storage for composites (structs/arrays) - /// so the clone is independent. Does not follow pointers. - fn cloneValue(self: *VM, val: Value) !Value { - return switch (val) { - .struct_val => |sv| { - const new_fields = try self.allocator.alloc(Value, sv.fields.len); - for (sv.fields, 0..) |f, i| { - new_fields[i] = try self.cloneValue(f); - } - return .{ .struct_val = .{ .type_name = sv.type_name, .field_names = sv.field_names, .fields = new_fields } }; - }, - .array_val => |av| { - const new_elements = try self.allocator.alloc(Value, av.elements.len); - for (av.elements, 0..) |e, i| { - new_elements[i] = try self.cloneValue(e); - } - return .{ .array_val = .{ .elements = new_elements } }; - }, - .union_val => |uv| { - const new_words = try self.allocator.alloc(Value, uv.words.len); - for (uv.words, 0..) |w, i| { - new_words[i] = try self.cloneValue(w); - } - return .{ .union_val = .{ .type_name = uv.type_name, .words = new_words } }; - }, - else => val, - }; - } - - fn valEqual(self: *VM, a: Value, b: Value) bool { - _ = self; - if (a == .int_val and b == .int_val) return a.int_val == b.int_val; - if (a == .bool_val and b == .bool_val) return a.bool_val == b.bool_val; - if (a == .string_val and b == .string_val) return std.mem.eql(u8, a.string_val, b.string_val); - // Pointer comparison (null_val == int_val(0) for null pointer compatibility) - if (a == .null_val and b == .null_val) return true; - if (a == .null_val and b == .int_val) return b.int_val == 0; - if (a == .int_val and b == .null_val) return a.int_val == 0; - if (a == .null_val or b == .null_val) return false; - if (a == .pointer_val and b == .pointer_val) return a.pointer_val.target == b.pointer_val.target; - // Float comparison - const af = a.asFloat(); - const bf = b.asFloat(); - if (af != null and bf != null) return af.? == bf.?; - return false; - } - - fn valLess(self: *VM, a: Value, b: Value) bool { - _ = self; - if (a == .int_val and b == .int_val) return a.int_val < b.int_val; - const af = a.asFloat(); - const bf = b.asFloat(); - if (af != null and bf != null) return af.? < bf.?; - return false; - } - - fn callFunction(self: *VM, name: []const u8, arg_count: u8) !void { - // Look up chunk in cache - if (self.functions.get(name)) |ptr| { - return self.invokeChunk(ptr, arg_count); - } - - // On-demand compilation: find function AST in root_decls - for (self.root_decls) |decl| { - switch (decl.data) { - .fn_decl => |fd| { - if (std.mem.eql(u8, fd.name, name)) - return self.compileFunctionAndInvoke(name, fd, arg_count); - }, - .namespace_decl => |ns| { - for (ns.decls) |d| { - if (d.data == .fn_decl and std.mem.eql(u8, d.data.fn_decl.name, name)) - return self.compileFunctionAndInvoke(name, d.data.fn_decl, arg_count); - } - }, - else => {}, - } - } - - // Resolve UFCS aliases first (comptime builtins like allocator_alloc need priority) - for (self.root_decls) |decl| { - switch (decl.data) { - .ufcs_alias => |ua| { - if (std.mem.eql(u8, ua.name, name)) { - // Check if target is a builtin - if (std.meta.stringToEnum(BuiltinId, ua.target)) |id| - return self.callBuiltin(id, arg_count); - return self.callFunction(ua.target, arg_count); - } - }, - .namespace_decl => |ns| { - for (ns.decls) |d| { - if (d.data == .ufcs_alias) { - const ua = d.data.ufcs_alias; - if (std.mem.eql(u8, ua.name, name)) { - if (std.meta.stringToEnum(BuiltinId, ua.target)) |id| - return self.callBuiltin(id, arg_count); - return self.callFunction(ua.target, arg_count); - } - } - } - }, - else => {}, - } - } - - // Search struct methods (after UFCS aliases, so builtins take priority) - for (self.root_decls) |decl| { - switch (decl.data) { - .struct_decl => |sd| { - for (sd.methods) |m| { - if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name)) - return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count); - } - }, - .namespace_decl => |ns| { - for (ns.decls) |d| { - if (d.data == .struct_decl) { - for (d.data.struct_decl.methods) |m| { - if (m.data == .fn_decl and std.mem.eql(u8, m.data.fn_decl.name, name)) - return self.compileFunctionAndInvoke(name, m.data.fn_decl, arg_count); - } - } - } - }, - else => {}, - } - } - - return error.UndefinedFunction; - } - - fn invokeChunk(self: *VM, chunk: *const Chunk, arg_count: u8) !void { - if (self.fp >= 64) return error.StackOverflow; - - // Args are on the stack. Set up new frame. - const base = self.sp - @as(u16, arg_count); - self.frames[self.fp] = .{ .chunk = chunk, .ip = 0, .base_slot = base }; - self.fp += 1; - } - - /// Execute a sub-chunk inline, sharing the current frame's stack base. - /// Used by #insert so generated code can access the caller's locals. - fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void { - switch (id) { - .out => { - // out(str) — raw string output - if (arg_count >= 1) { - const val = try self.pop(); - const str = try val.format(self.allocator); - std.debug.print("{s}", .{str}); - } - try self.push(.{ .void_val = {} }); - }, - .print => { - // print(fmt, args...) — positional {} formatting - if (arg_count == 0) { - try self.push(.{ .void_val = {} }); - } else if (arg_count == 1) { - // Single arg: just print it - const val = try self.pop(); - const str = try val.format(self.allocator); - std.debug.print("{s}", .{str}); - try self.push(.{ .void_val = {} }); - } else { - // Pop args in reverse order (stack is LIFO) - var vals = std.ArrayList(Value).empty; - var j: u8 = 0; - while (j < arg_count) : (j += 1) { - try vals.append(self.allocator, try self.pop()); - } - // vals[0] is last arg, vals[arg_count-1] is first (format string) - const fmt_val = vals.items[arg_count - 1]; - const fmt_str = try fmt_val.format(self.allocator); - - // Process format string with {} placeholders - var out = std.ArrayList(u8).empty; - var arg_idx: usize = 0; - var fi: usize = 0; - while (fi < fmt_str.len) { - if (fi + 1 < fmt_str.len and fmt_str[fi] == '{' and fmt_str[fi + 1] == '}') { - if (arg_idx < arg_count - 1) { - // vals are in reverse: vals[arg_count-2] is first value arg, vals[0] is last - const val_idx = arg_count - 2 - arg_idx; - const formatted = try vals.items[val_idx].format(self.allocator); - try out.appendSlice(self.allocator, formatted); - arg_idx += 1; - } - fi += 2; - } else if (fi + 1 < fmt_str.len and fmt_str[fi] == '{' and fmt_str[fi + 1] == '{') { - try out.append(self.allocator, '{'); - fi += 2; - } else if (fi + 1 < fmt_str.len and fmt_str[fi] == '}' and fmt_str[fi + 1] == '}') { - try out.append(self.allocator, '}'); - fi += 2; - } else { - try out.append(self.allocator, fmt_str[fi]); - fi += 1; - } - } - std.debug.print("{s}", .{out.items}); - try self.push(.{ .void_val = {} }); - } - }, - .sqrt => { - if (arg_count >= 1) { - const val = try self.pop(); - const f = val.asFloat() orelse return error.TypeError; - try self.push(.{ .float_val = @sqrt(f) }); - } else { - try self.push(.{ .float_val = 0.0 }); - } - }, - .size_of => { - if (arg_count >= 1) { - const val = try self.pop(); - if (val == .type_val) { - const size = sizeOfType(val.type_val, self.codegen); - try self.push(.{ .int_val = @intCast(size) }); - } else { - try self.push(.{ .int_val = 0 }); - } - } else { - try self.push(.{ .int_val = 0 }); - } - }, - .cast => { - // cast(Type, val) — explicit type conversion - if (arg_count >= 2) { - const val = try self.pop(); // second arg (value) - const type_arg = try self.pop(); // first arg (type) - const target_ty: Type = if (type_arg == .type_val) type_arg.type_val else .void_type; - // Convert based on target type - if (target_ty.isFloat()) { - // Target is float — convert from int or other float - switch (val) { - .int_val => |v| try self.push(.{ .float_val = @floatFromInt(v) }), - .float32_val => |v| try self.push(.{ .float_val = @as(f64, v) }), - .float_val => try self.push(val), - else => try self.push(val), - } - } else if (target_ty.isInt()) { - // Target is int — convert from float - switch (val) { - .float_val => |v| try self.push(.{ .int_val = @intFromFloat(v) }), - .float32_val => |v| try self.push(.{ .int_val = @intFromFloat(v) }), - .int_val => try self.push(val), - else => try self.push(val), - } - } else { - try self.push(val); // pass through - } - } else { - try self.push(.{ .int_val = 0 }); - } - }, - .malloc => { - // malloc(size) — allocate byte buffer, return as byte_ptr_val - if (arg_count >= 1) { - const val = try self.pop(); - const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0; - const buf = try self.allocator.alloc(u8, size); - @memset(buf, 0); - try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } }); - } else { - try self.push(.{ .byte_ptr_val = .{ .data = &.{}, .offset = 0 } }); - } - }, - .free => { - // free(ptr) — no-op at comptime (arena cleanup) - if (arg_count >= 1) _ = try self.pop(); - try self.push(.{ .void_val = {} }); - }, - .memcpy => { - // memcpy(dst, src, len) — copy len bytes from src to dst - if (arg_count >= 3) { - const len_val = try self.pop(); - const src_val = try self.pop(); - const dst_val = try self.pop(); - const len: usize = if (len_val.asInt()) |v| @intCast(@max(0, v)) else 0; - if (dst_val == .byte_ptr_val and src_val == .byte_ptr_val) { - const dst = dst_val.byte_ptr_val; - const src = src_val.byte_ptr_val; - @memcpy(dst.data[dst.offset .. dst.offset + len], src.data[src.offset .. src.offset + len]); - } - } - try self.push(.{ .void_val = {} }); - }, - .memset => { - // memset(dst, val, len) — fill len bytes at dst with val - if (arg_count >= 3) { - const len_val = try self.pop(); - const val = try self.pop(); - const dst_val = try self.pop(); - const len: usize = if (len_val.asInt()) |v| @intCast(@max(0, v)) else 0; - const byte: u8 = if (val.asInt()) |v| @intCast(v & 0xFF) else 0; - if (dst_val == .byte_ptr_val) { - const dst = dst_val.byte_ptr_val; - @memset(dst.data[dst.offset .. dst.offset + len], byte); - } - } - try self.push(.{ .void_val = {} }); - }, - .alloc => { - // alloc(size) or alloc(allocator, size) — at comptime, equivalent to malloc - if (arg_count >= 2) { - const size_val = try self.pop(); - _ = try self.pop(); // discard allocator struct - const size: usize = if (size_val.asInt()) |v| @intCast(@max(0, v)) else 0; - const buf = try self.allocator.alloc(u8, size); - @memset(buf, 0); - try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } }); - } else if (arg_count == 1) { - const val = try self.pop(); - const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0; - const buf = try self.allocator.alloc(u8, size); - @memset(buf, 0); - try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } }); - } else { - try self.push(.{ .byte_ptr_val = .{ .data = &.{}, .offset = 0 } }); - } - }, - .dealloc => { - // dealloc(ptr) — at comptime, no-op - var i: u8 = 0; - while (i < arg_count) : (i += 1) _ = try self.pop(); - try self.push(.{ .void_val = {} }); - }, - .type_of => { - // type_of(val) — return the type tag (matching ANY_TAG_* constants) - if (arg_count >= 1) { - const val = try self.pop(); - const tag: i64 = switch (val) { - .any_val => |av| av.tag, - .void_val => 0, - .bool_val => 1, - .int_val => 3, - .float32_val => 4, - .float_val => 5, - .string_val => 6, - .type_val => 10, - else => 0, - }; - try self.push(.{ .int_val = tag }); - } else { - try self.push(.{ .int_val = 0 }); - } - }, - } - } - - /// Resolve a global variable by name. Checks the globals cache first, - /// then searches root_decls for matching const_decl/var_decl and evaluates. - const VMError = error{ - CompileError, - UndefinedVariable, - UndefinedFunction, - InvalidGlobal, - InvalidCallee, - TypeError, - StackOverflow, - StackUnderflow, - IndexOutOfBounds, - DivisionByZero, - NullDereference, - UnsupportedExpression, - OutOfMemory, - }; - - /// Evaluate a chunk in a fresh VM to avoid corrupting this VM's state. - fn evalInFreshVM(self: *VM, chunk: *const Chunk) VMError!Value { - var nested_vm = VM.init(self.allocator, self.sema_result, self.root_decls, self.codegen); - // Share the globals cache so nested evaluations see already-resolved globals - nested_vm.globals = self.globals; - const result = nested_vm.execute(chunk) catch return error.CompileError; - // Copy back any new globals that were resolved during nested evaluation - self.globals = nested_vm.globals; - return result; - } - - fn cacheTypeGlobal(self: *VM, name: []const u8, ty: Type) VMError!Value { - const val = Value{ .type_val = ty }; - try self.globals.put(name, val); - return val; - } - - fn compileAndEvalGlobal(self: *VM, name: []const u8, expr: *Node) VMError!Value { - var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); - const chunk = compiler.compile(expr) catch return error.CompileError; - const result = self.evalInFreshVM(&chunk) catch return error.CompileError; - try self.globals.put(name, result); - return result; - } - - pub fn compileFunctionAndInvoke(self: *VM, name: []const u8, fd: ast.FnDecl, arg_count: u8) !void { - // Check for variadic parameter and pack extra args into an array - var effective_arg_count = arg_count; - var variadic_idx: ?usize = null; - var fixed_count: u8 = 0; - for (fd.params, 0..) |param, i| { - if (param.is_variadic) { - variadic_idx = i; - break; - } - fixed_count += 1; - } - - if (variadic_idx != null and arg_count >= fixed_count) { - // Pop all args from stack (in reverse order) - const total = @as(usize, arg_count); - var all_args = try self.allocator.alloc(Value, total); - var i: usize = total; - while (i > 0) { - i -= 1; - all_args[i] = try self.pop(); - } - - // Push fixed args back - for (all_args[0..fixed_count]) |arg| { - try self.push(arg); - } - - // Box variadic args as any_val and pack into array_val - const variadic_count = total - fixed_count; - var elements = try self.allocator.alloc(Value, variadic_count); - for (0..variadic_count) |vi| { - elements[vi] = try all_args[fixed_count + vi].boxAsAny(self.allocator); - } - try self.push(.{ .array_val = .{ .elements = elements } }); - effective_arg_count = fixed_count + 1; // fixed params + 1 array - } - - var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); - const chunk = try compiler.compileFunction(fd); - const heap_chunk = try self.allocator.create(Chunk); - heap_chunk.* = chunk; - try self.functions.put(name, heap_chunk); - return self.invokeChunk(heap_chunk, effective_arg_count); - } - - fn resolveGlobal(self: *VM, name: []const u8) VMError!Value { - // Check cache first - if (self.globals.get(name)) |val| return val; - - // Search root_decls for matching declaration - for (self.root_decls) |decl| { - switch (decl.data) { - .const_decl => |cd| { - if (std.mem.eql(u8, cd.name, name)) { - return self.compileAndEvalGlobal(name, cd.value); - } - }, - .var_decl => |vd| { - if (std.mem.eql(u8, vd.name, name)) { - if (vd.value) |val_expr| { - // undef_literal (= ---) means zeroed/undefined — return void - if (val_expr.data == .undef_literal) - return .{ .void_val = {} }; - return self.compileAndEvalGlobal(name, val_expr); - } - return .{ .void_val = {} }; - } - }, - .namespace_decl => |ns| { - // Check inside namespace for matching declarations - for (ns.decls) |d| { - if (d.data == .const_decl and std.mem.eql(u8, d.data.const_decl.name, name)) { - return self.compileAndEvalGlobal(name, d.data.const_decl.value); - } - } - }, - else => {}, - } - } - - // Check for struct/enum/union type declarations - for (self.root_decls) |decl| { - switch (decl.data) { - .struct_decl => |sd| { - if (std.mem.eql(u8, sd.name, name)) - return self.cacheTypeGlobal(name, .{ .struct_type = name }); - }, - .enum_decl => |ed| { - if (std.mem.eql(u8, ed.name, name)) { - const ty: Type = if (ed.variant_types.len > 0) .{ .union_type = name } else .{ .enum_type = name }; - return self.cacheTypeGlobal(name, ty); - } - }, - .union_decl => |ud| { - if (std.mem.eql(u8, ud.name, name)) - return self.cacheTypeGlobal(name, .{ .union_type = name }); - }, - else => {}, - } - } - - // Check if it's a primitive type name (s32, f64, bool, etc.) - if (Type.fromName(name)) |ty| - return self.cacheTypeGlobal(name, ty); - - // Type category tags for match expressions on Any values - const type_tag: ?i64 = if (std.mem.eql(u8, name, "void")) 0 - else if (std.mem.eql(u8, name, "bool")) 1 - else if (std.mem.eql(u8, name, "int")) 3 - else if (std.mem.eql(u8, name, "float")) 5 - else if (std.mem.eql(u8, name, "string")) 6 - else if (std.mem.eql(u8, name, "type")) 10 - else if (std.mem.eql(u8, name, "struct")) 11 - else if (std.mem.eql(u8, name, "enum")) 12 - else if (std.mem.eql(u8, name, "vector")) 13 - else if (std.mem.eql(u8, name, "array")) 14 - else if (std.mem.eql(u8, name, "slice")) 15 - else if (std.mem.eql(u8, name, "pointer")) 16 - else null; - if (type_tag) |tag| { - const val = Value{ .int_val = tag }; - self.globals.put(name, val) catch {}; - return val; - } - - return error.UndefinedVariable; - } -}; - -test "Value: basic operations" { - const a = Value{ .int_val = 42 }; - try std.testing.expect(a.isInt()); - try std.testing.expect(!a.isFloat()); - try std.testing.expectEqual(@as(i64, 42), a.asInt().?); - try std.testing.expectEqual(@as(f64, 42.0), a.asFloat().?); - - const b = Value{ .float_val = 3.14 }; - try std.testing.expect(!b.isInt()); - try std.testing.expect(b.isFloat()); - try std.testing.expectEqual(@as(f64, 3.14), b.asFloat().?); - - const c = Value{ .bool_val = true }; - try std.testing.expectEqual(@as(i64, 1), c.asInt().?); -} - -const parser_mod = @import("parser.zig"); - -fn compileAndRun(source: [:0]const u8) !Value { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - var p = parser_mod.Parser.init(alloc, source); - const expr = try p.parseExpr(); - - var compiler = Compiler.init(alloc, null, &.{}, null); - const chunk = try compiler.compile(expr); - - var vm = VM.init(alloc, null, &.{}, null); - return vm.execute(&chunk); -} - -test "VM: 2 + 3 = 5" { - const result = try compileAndRun("2 + 3"); - try std.testing.expectEqual(@as(i64, 5), result.int_val); -} - -test "VM: arithmetic operations" { - // subtraction - const r1 = try compileAndRun("10 - 3"); - try std.testing.expectEqual(@as(i64, 7), r1.int_val); - - // multiplication - const r2 = try compileAndRun("6 * 7"); - try std.testing.expectEqual(@as(i64, 42), r2.int_val); - - // division - const r3 = try compileAndRun("20 / 4"); - try std.testing.expectEqual(@as(i64, 5), r3.int_val); - - // negation - const r4 = try compileAndRun("-42"); - try std.testing.expectEqual(@as(i64, -42), r4.int_val); -} - -test "VM: comparison operations" { - const r1 = try compileAndRun("3 == 3"); - try std.testing.expectEqual(true, r1.bool_val); - - const r2 = try compileAndRun("3 != 4"); - try std.testing.expectEqual(true, r2.bool_val); - - const r3 = try compileAndRun("2 < 5"); - try std.testing.expectEqual(true, r3.bool_val); - - const r4 = try compileAndRun("5 > 2"); - try std.testing.expectEqual(true, r4.bool_val); -} - -test "VM: boolean literals" { - const r1 = try compileAndRun("true"); - try std.testing.expectEqual(true, r1.bool_val); - - const r2 = try compileAndRun("false"); - try std.testing.expectEqual(false, r2.bool_val); - - const r3 = try compileAndRun("!false"); - try std.testing.expectEqual(true, r3.bool_val); -} - -test "VM: float arithmetic" { - const r1 = try compileAndRun("1.5 + 2.5"); - try std.testing.expectEqual(@as(f64, 4.0), r1.float_val); - - const r2 = try compileAndRun("3.0 * 2.0"); - try std.testing.expectEqual(@as(f64, 6.0), r2.float_val); -} - -test "VM: if expression" { - const r1 = try compileAndRun("if true then 1 else 2"); - try std.testing.expectEqual(@as(i64, 1), r1.int_val); - - const r2 = try compileAndRun("if false then 1 else 2"); - try std.testing.expectEqual(@as(i64, 2), r2.int_val); -} - -test "VM: block with variables" { - // Parse a block expression: { x := 5; y := x + 3; y; } - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Parse a block as a statement sequence - var p = parser_mod.Parser.init(alloc, "{ x := 5; y := x + 3; y; }"); - const expr = try p.parseExpr(); - - var compiler = Compiler.init(alloc, null, &.{}, null); - const chunk = try compiler.compile(expr); - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 8), result.int_val); -} - -test "VM: nested if with variables" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - var p = parser_mod.Parser.init(alloc, "{ x := 10; if x > 5 then 1 else 0; }"); - const expr = try p.parseExpr(); - - var compiler = Compiler.init(alloc, null, &.{}, null); - const chunk = try compiler.compile(expr); - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 1), result.int_val); -} - -/// Helper to compile and run a full program, executing a specific expression -/// after all declarations are registered. -fn compileAndRunProgram(source: [:0]const u8, expr_source: [:0]const u8) !Value { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Parse the full program to get root decls - var prog_parser = parser_mod.Parser.init(alloc, source); - const root = try prog_parser.parse(); - const decls = root.data.root.decls; - - // Parse the expression to evaluate - var expr_parser = parser_mod.Parser.init(alloc, expr_source); - const expr = try expr_parser.parseExpr(); - - var compiler = Compiler.init(alloc, null, decls, null); - const chunk = try compiler.compile(expr); - - var vm = VM.init(alloc, null, decls, null); - return vm.execute(&chunk); -} - -test "VM: function call" { - const result = try compileAndRunProgram( - "add :: (a: s32, b: s32) -> s32 { a + b; }", - "add(2, 3)", - ); - try std.testing.expectEqual(@as(i64, 5), result.int_val); -} - -test "VM: nested function calls" { - const result = try compileAndRunProgram( - "double :: (x: s32) -> s32 { x * 2; } quad :: (x: s32) -> s32 { double(double(x)); }", - "quad(3)", - ); - try std.testing.expectEqual(@as(i64, 12), result.int_val); -} - -test "VM: match expression" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Match on integer value - var p = parser_mod.Parser.init(alloc, "{ x := 2; if x == { case 1: 10; case 2: 20; case 3: 30; } }"); - const expr = try p.parseExpr(); - - var compiler = Compiler.init(alloc, null, &.{}, null); - const chunk = try compiler.compile(expr); - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 20), result.int_val); -} - -test "VM: builtin sqrt" { - const result = try compileAndRun("sqrt(16.0)"); - try std.testing.expectEqual(@as(f64, 4.0), result.float_val); -} - -test "VM: struct literal and field access" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Manually build a chunk that creates a struct and reads field 1 - const code = [_]Instruction{ - .{ .push_int = 10 }, // field 0: x - .{ .push_int = 20 }, // field 1: y - .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, - .{ .get_field = 1 }, // get y - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 0, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 20), result.int_val); -} - -test "VM: array literal and index" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Manually build: make_array([10, 20, 30]), get_index(1) - const code = [_]Instruction{ - .{ .push_int = 10 }, - .{ .push_int = 20 }, - .{ .push_int = 30 }, - .{ .make_array = 3 }, - .{ .push_int = 1 }, // index - .get_index, - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 0, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 20), result.int_val); -} - -test "VM: string concat" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - const strings = [_][]const u8{ "hello ", "world" }; - const code = [_]Instruction{ - .{ .push_string = 0 }, - .{ .push_string = 1 }, - .concat, - }; - const chunk = Chunk{ - .code = &code, - .strings = &strings, - .local_count = 0, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqualStrings("hello world", result.string_val); -} - -test "VM: type value" { - const result = try compileAndRun("f64"); - try std.testing.expect(result == .type_val); -} - -test "VM: function with return statement" { - const result = try compileAndRunProgram( - "compute :: (x: s32) -> s32 { return x * x; }", - "compute(6)", - ); - try std.testing.expectEqual(@as(i64, 36), result.int_val); -} - -test "VM: address-of and deref" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // x := 42; ptr := @x; ptr.* - const code = [_]Instruction{ - .{ .push_int = 42 }, - .{ .set_local = 0 }, // x = 42 - .{ .address_of_local = 0 }, // &x - .{ .set_local = 1 }, // ptr = &x - .{ .get_local = 1 }, // ptr - .deref, // ptr.* - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 2, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 42), result.int_val); -} - -test "VM: deref_set modifies through pointer" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // x := 10; ptr := @x; ptr.* = 99; x - const code = [_]Instruction{ - .{ .push_int = 10 }, - .{ .set_local = 0 }, // x = 10 - .{ .address_of_local = 0 }, // &x - .{ .set_local = 1 }, // ptr = &x - .{ .get_local = 1 }, // ptr - .{ .push_int = 99 }, - .deref_set, // ptr.* = 99 - .{ .get_local = 0 }, // x (should be 99 now) - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 2, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 99), result.int_val); -} - -test "VM: null pointer" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - const code = [_]Instruction{ - .push_null, - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 0, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expect(result == .null_val); -} - -test "VM: pointer to struct field access" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // Build: struct{x: 10, y: 20}, @struct, get_field(1) — auto-deref - const code = [_]Instruction{ - .{ .push_int = 10 }, - .{ .push_int = 20 }, - .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, - .{ .set_local = 0 }, // v = Point{10, 20} - .{ .address_of_local = 0 }, // &v - .{ .get_field = 1 }, // auto-deref, get y - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 1, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 20), result.int_val); -} - -test "VM: pointer to struct field mutation" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // v = Point{10, 20}; ptr = &v; ptr.x = 99; v.x - const code = [_]Instruction{ - .{ .push_int = 10 }, - .{ .push_int = 20 }, - .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, - .{ .set_local = 0 }, // v = Point{10, 20} - .{ .address_of_local = 0 }, // &v - .{ .set_local = 1 }, // ptr = &v - .{ .get_local = 1 }, // ptr - .{ .push_int = 99 }, - .{ .set_field = 0 }, // ptr.x = 99 (auto-deref) - .{ .set_local = 1 }, // store ptr back - .{ .get_local = 0 }, // v - .{ .get_field = 0 }, // v.x - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 2, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 99), result.int_val); -} - -test "VM: many-pointer indexing" { - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - // arr = [10, 20, 30]; mp = &arr[0]; mp[2] - const code = [_]Instruction{ - .{ .push_int = 10 }, - .{ .push_int = 20 }, - .{ .push_int = 30 }, - .{ .make_array = 3 }, - .{ .set_local = 0 }, // arr = [10, 20, 30] - .{ .get_local = 0 }, // arr - .{ .push_int = 0 }, - .address_of_index, // &arr[0] - .{ .set_local = 1 }, // mp = &arr[0] - .{ .get_local = 1 }, // mp - .{ .push_int = 2 }, - .get_index, // mp[2] - }; - const chunk = Chunk{ - .code = &code, - .strings = &.{}, - .local_count = 2, - .name = "", - }; - - var vm = VM.init(alloc, null, &.{}, null); - const result = try vm.execute(&chunk); - try std.testing.expectEqual(@as(i64, 30), result.int_val); -} diff --git a/src/core.zig b/src/core.zig index 51facd9..fb67b2e 100644 --- a/src/core.zig +++ b/src/core.zig @@ -3,13 +3,13 @@ const ast = @import("ast.zig"); const parser = @import("parser.zig"); const imports = @import("imports.zig"); const sema = @import("sema.zig"); -const codegen = @import("codegen.zig"); const errors = @import("errors.zig"); const c_import = @import("c_import.zig"); const ir = @import("ir/ir.zig"); +const target_mod = @import("target.zig"); const Node = ast.Node; -pub const TargetConfig = codegen.TargetConfig; +pub const TargetConfig = target_mod.TargetConfig; pub const Compilation = struct { allocator: std.mem.Allocator, @@ -24,7 +24,6 @@ pub const Compilation = struct { resolved_root: ?*Node = null, import_sources: std.StringHashMap([:0]const u8), sema_result: ?sema.SemaResult = null, - cg: ?codegen.CodeGen = null, ir_emitter: ?ir.LLVMEmitter = null, pub fn init(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8, source: [:0]const u8, target_config: TargetConfig) Compilation { @@ -41,7 +40,6 @@ pub const Compilation = struct { pub fn deinit(self: *Compilation) void { if (self.ir_emitter) |*e| e.deinit(); - if (self.cg) |*cg| cg.deinit(); self.diagnostics.deinit(); } @@ -95,21 +93,8 @@ pub const Compilation = struct { } } - pub fn generateCode(self: *Compilation) !void { - const root = self.resolved_root orelse self.root orelse return error.CompileError; - var cg = codegen.CodeGen.init(self.allocator, "sx_module", self.target_config); - cg.diagnostics = &self.diagnostics; - cg.import_sources = &self.import_sources; - if (self.sema_result) |*sr| { - cg.sema_result = sr; - } - cg.generate(root) catch return error.CompileError; - - self.cg = cg; - } - /// Generate code via the IR pipeline: lower AST → IR → LLVM. - pub fn generateCodeViaIR(self: *Compilation) !void { + pub fn generateCode(self: *Compilation) !void { // Heap-allocate the IR module so its address is stable during emit const ir_mod_ptr = try self.allocator.create(ir.Module); ir_mod_ptr.* = self.lowerToIR(); @@ -122,7 +107,6 @@ pub const Compilation = struct { } /// Collect C import source info from the resolved AST. - /// Called after generateCode() to compile C sources natively (not merged into LLVM module). pub fn collectCImportSources(self: *Compilation) ![]c_import.CImportInfo { const root = self.resolved_root orelse self.root orelse return &.{}; return c_import.collectCImportSources(self.allocator, root); diff --git a/src/ir/emit_llvm.test.zig b/src/ir/emit_llvm.test.zig index 47184be..e6c9cda 100644 --- a/src/ir/emit_llvm.test.zig +++ b/src/ir/emit_llvm.test.zig @@ -437,7 +437,7 @@ test "emit: struct_gep (pointer to field)" { b.switchToBlock(entry); const p_ptr = b.alloca(point_ty); - const y_ptr = b.structGep(p_ptr, 1, ptr_s64); + const y_ptr = b.structGepTyped(p_ptr, 1, ptr_s64, point_ty); const c42 = b.constInt(42, .s64); b.store(y_ptr, c42); const loaded = b.load(y_ptr, .s64); @@ -482,7 +482,7 @@ test "emit: enum_init and enum_tag (plain enum)" { b.switchToBlock(entry); const green = b.enumInit(1, Ref.none, color_ty); // Green = tag 1 - const tag = b.enumTag(green); + const tag = b.enumTag(green, .s32); // Widen tag from s32 to s64 for the return const wide = b.widen(tag, .s32, .s64); b.ret(wide, .s64); @@ -511,10 +511,10 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" { }; const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable; defer alloc.free(owned_ufields); - const shape_ty = module.types.intern(.{ .@"union" = .{ + const shape_ty = module.types.intern(.{ .tagged_union = .{ .name = str(&module, "Shape"), .fields = owned_ufields, - .tag_type = null, + .tag_type = .s64, } }); var b = Builder.init(&module); @@ -558,7 +558,6 @@ test "emit: union_get (reinterpret union field)" { const data_ty = module.types.intern(.{ .@"union" = .{ .name = str(&module, "Data"), .fields = owned_ufields, - .tag_type = null, } }); var b = Builder.init(&module); diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 83d8329..17f215a 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -2,8 +2,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const llvm = @import("../llvm_api.zig"); const c = llvm.c; -const codegen = @import("../codegen.zig"); -const TargetConfig = codegen.TargetConfig; +const target_mod = @import("../target.zig"); +const TargetConfig = target_mod.TargetConfig; const ir_types = @import("types.zig"); const TypeId = ir_types.TypeId; const TypeInfo = ir_types.TypeInfo; @@ -188,6 +188,29 @@ pub const LLVMEmitter = struct { if (func.is_extern or func.blocks.items.len == 0) continue; self.emitFunction(&func, @intCast(i)); } + + // Pass 3: Verify typeSizeBytes matches LLVM's ABI sizes + self.verifySizes(); + } + + /// Compare IR typeSizeBytes against LLVMABISizeOfType for all user-defined types. + fn verifySizes(self: *LLVMEmitter) void { + const dl = c.LLVMGetModuleDataLayout(self.llvm_module); + if (dl == null) return; + const type_count = self.ir_mod.types.infos.items.len; + for (TypeId.first_user..type_count) |idx| { + const ty = TypeId.fromIndex(@intCast(idx)); + const info = self.ir_mod.types.get(ty); + // Only verify aggregate types where sizing is non-trivial + switch (info) { + .@"struct", .@"union", .tagged_union, .tuple => {}, + else => continue, + } + const llvm_ty = self.toLLVMType(ty); + const llvm_size = c.LLVMABISizeOfType(dl, llvm_ty); + const ir_size = self.ir_mod.types.typeSizeBytes(ty); + std.debug.assert(llvm_size == ir_size); + } } /// Run comptime side-effect functions (e.g., `#run main();` at top level). @@ -241,6 +264,7 @@ pub const LLVMEmitter = struct { .int => |v| c.LLVMConstInt(llvm_ty, @bitCast(v), 1), .float => |v| c.LLVMConstReal(llvm_ty, v), .boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0), + .string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)), else => c.LLVMConstNull(llvm_ty), }; c.LLVMSetInitializer(llvm_global, init_val); @@ -269,14 +293,16 @@ pub const LLVMEmitter = struct { const is_main = std.mem.eql(u8, name, "main"); // main always returns i32 at the LLVM level (JIT expects it) - const ret_ty = if (is_main) self.cached_i32 else self.toLLVMType(func.ret); + const raw_ret_ty = self.toLLVMType(func.ret); + const ret_ty = if (is_main) self.cached_i32 else if (func.is_extern) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty; - // Build parameter types + // Build parameter types — apply C ABI coercion for foreign (extern) functions const param_count: c_uint = @intCast(func.params.len); const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable; defer self.alloc.free(param_types); for (func.params, 0..) |param, j| { - param_types[j] = self.toLLVMType(param.ty); + const llvm_ty = self.toLLVMType(param.ty); + param_types[j] = if (func.is_extern) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty; } const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0); @@ -356,7 +382,8 @@ pub const LLVMEmitter = struct { // (blocks may not be in emission order due to nested control flow) self.ref_counter = block.first_ref; - for (block.insts.items) |instruction| { + for (block.insts.items, 0..) |instruction, inst_i| { + _ = inst_i; self.emitInst(&instruction, func_idx); } } @@ -528,18 +555,21 @@ pub const LLVMEmitter = struct { // ── Bitwise ──────────────────────────────────────────── .bit_and => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); + var lhs = self.resolveRef(bin.lhs); + var rhs = self.resolveRef(bin.rhs); + self.matchBinOpTypes(&lhs, &rhs, instruction.ty); self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "and")); }, .bit_or => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); + var lhs = self.resolveRef(bin.lhs); + var rhs = self.resolveRef(bin.rhs); + self.matchBinOpTypes(&lhs, &rhs, instruction.ty); self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "or")); }, .bit_xor => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); + var lhs = self.resolveRef(bin.lhs); + var rhs = self.resolveRef(bin.rhs); + self.matchBinOpTypes(&lhs, &rhs, instruction.ty); self.mapRef(c.LLVMBuildXor(self.builder, lhs, rhs, "xor")); }, .bit_not => |un| { @@ -547,13 +577,15 @@ pub const LLVMEmitter = struct { self.mapRef(c.LLVMBuildNot(self.builder, operand, "not")); }, .shl => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); + var lhs = self.resolveRef(bin.lhs); + var rhs = self.resolveRef(bin.rhs); + self.matchBinOpTypes(&lhs, &rhs, instruction.ty); self.mapRef(c.LLVMBuildShl(self.builder, lhs, rhs, "shl")); }, .shr => |bin| { - const lhs = self.resolveRef(bin.lhs); - const rhs = self.resolveRef(bin.rhs); + var lhs = self.resolveRef(bin.lhs); + var rhs = self.resolveRef(bin.rhs); + self.matchBinOpTypes(&lhs, &rhs, instruction.ty); // Use arithmetic shift right for signed, logical for unsigned const result = if (isSignedType(instruction.ty)) c.LLVMBuildAShr(self.builder, lhs, rhs, "ashr") @@ -746,6 +778,15 @@ pub const LLVMEmitter = struct { if (result.asInt()) |v| { self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0)); return; + } else if (result.asFloat()) |v| { + self.mapRef(c.LLVMConstReal(self.toLLVMType(instruction.ty), v)); + return; + } else if (result.asBool()) |v| { + self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @intFromBool(v), 0)); + return; + } else if (result == .string) { + self.mapRef(self.emitStringConstant(result.string)); + return; } } else |_| {} } @@ -770,7 +811,12 @@ pub const LLVMEmitter = struct { args[j] = self.coerceArg(args[j], param_types[j]); } } - const result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (instruction.ty == .void) "" else "call"); + var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (instruction.ty == .void) "" else "call"); + // Coerce ABI return value (e.g. i64) back to IR struct type if needed + if (instruction.ty != .void and callee_func.is_extern) { + const expected_ty = self.toLLVMType(instruction.ty); + result = self.coerceArg(result, expected_ty); + } self.mapRef(result); }, .call_indirect => |call_op| { @@ -813,7 +859,11 @@ pub const LLVMEmitter = struct { if (fn_params) |fp| { for (0..call_op.args.len) |j| { if (j < fp.len) { - const llvm_pty = self.toLLVMType(fp[j]); + var llvm_pty = self.toLLVMType(fp[j]); + // Array params in fn-ptr calls decay to pointers (C ABI) + if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) { + llvm_pty = self.cached_ptr; + } param_tys[j] = llvm_pty; args[j] = self.coerceArg(args[j], llvm_pty); } else { @@ -983,7 +1033,10 @@ pub const LLVMEmitter = struct { // Safety: verify base is a pointer before GEP const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); if (base_ty_kind == c.LLVMPointerTypeKind) { - const struct_llvm_ty = self.resolveGepStructType(fa.base, instruction); + const struct_llvm_ty = if (fa.base_type) |bt| + self.toLLVMType(self.resolveAggregate(bt)) + else + self.resolveGepStructType(fa.base, instruction); const st_kind = c.LLVMGetTypeKind(struct_llvm_ty); if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) { const result = c.LLVMBuildStructGEP2(self.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep"); @@ -1006,8 +1059,9 @@ pub const LLVMEmitter = struct { // Plain enum or builtin integer → integer constant self.mapRef(c.LLVMConstInt(ty, ei.tag, 0)); } else if (ty_kind == c.LLVMStructTypeKind) { - // Tagged union with no payload — store tag into union struct - const tag_val = c.LLVMConstInt(self.cached_i64, ei.tag, 0); + // Tagged union with no payload — header field 0 holds the tag + const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); var result = c.LLVMGetUndef(ty); result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag"); self.mapRef(result); @@ -1015,9 +1069,10 @@ pub const LLVMEmitter = struct { self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0)); } } else { - // Tagged union with payload — { tag, payload_bytes } + // Tagged union with payload — { header, payload_bytes } const union_ty = self.toLLVMType(instruction.ty); - const tag_val = c.LLVMConstInt(self.cached_i64, ei.tag, 0); + const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); const payload_val = self.resolveRef(ei.payload); // alloca union, store tag, bitcast payload area, store payload @@ -1040,7 +1095,17 @@ pub const LLVMEmitter = struct { const kind = c.LLVMGetTypeKind(val_ty); if (kind == c.LLVMStructTypeKind) { // Tagged union — extract field 0 (tag) - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "etag")); + var tag = c.LLVMBuildExtractValue(self.builder, val, 0, "etag"); + // Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags) + // This is essential for FFI unions where the i64 tag slot contains + // a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved) + const target_ty = self.toLLVMType(instruction.ty); + const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag)); + const target_bits = c.LLVMGetIntTypeWidth(target_ty); + if (target_bits < extracted_bits) { + tag = c.LLVMBuildTrunc(self.builder, tag, target_ty, "etag.trunc"); + } + self.mapRef(tag); } else { // Plain enum — the value IS the tag self.mapRef(val); @@ -1071,28 +1136,34 @@ pub const LLVMEmitter = struct { const base_ty = c.LLVMTypeOf(base); const kind = c.LLVMGetTypeKind(base_ty); if (kind == c.LLVMStructTypeKind) { - // { tag, payload_bytes } — extract payload then bitcast + // Tagged union { header, payload_bytes } — access payload at field 1 const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); _ = c.LLVMBuildStore(self.builder, base, tmp); const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp"); - const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ug.cast"); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, typed_ptr, "ug.val")); + self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, payload_ptr, "ug.val")); } else { - // Plain reinterpret - self.mapRef(c.LLVMBuildBitCast(self.builder, base, result_ty, "ug.cast")); + // Untagged union [N x i8] — alloca, store, reinterpret-load + const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); + _ = c.LLVMBuildStore(self.builder, base, tmp); + self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, tmp, "ug.val")); } }, .union_gep => |fa| { const base_ptr = self.resolveRef(fa.base); const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); if (base_ty_kind == c.LLVMPointerTypeKind) { - const union_llvm_ty = self.resolveGepStructType(fa.base, instruction); + const union_llvm_ty = if (fa.base_type) |bt| + self.toLLVMType(self.resolveAggregate(bt)) + else + self.resolveGepStructType(fa.base, instruction); const st_kind = c.LLVMGetTypeKind(union_llvm_ty); if (st_kind == c.LLVMStructTypeKind) { + // Tagged union — payload is at field 1 const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_llvm_ty, base_ptr, 1, "ugep.pp"); - self.mapRef(c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ugep.cast")); + self.mapRef(payload_ptr); } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); + // Untagged union — data starts at offset 0 + self.mapRef(base_ptr); } } else { self.mapRef(c.LLVMGetUndef(self.cached_ptr)); @@ -1317,21 +1388,19 @@ pub const LLVMEmitter = struct { _ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, ""); self.advanceRefCounter(); }, - .sqrt => { + .sqrt, .sin, .cos, .floor => { const val = self.resolveRef(bi.args[0]); const val_ty = c.LLVMTypeOf(val); const val_kind = c.LLVMGetTypeKind(val_ty); if (val_kind == c.LLVMFloatTypeKind) { - // f32 → sqrtf - const sqrtf_fn = self.getOrDeclareSqrtf(); + const f = self.getOrDeclareMathF32(bi.builtin); var args = [_]c.LLVMValueRef{val}; - self.mapRef(c.LLVMBuildCall2(self.builder, self.getSqrtfType(), sqrtf_fn, &args, 1, "sqrtf")); + self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF32Type(), f, &args, 1, @tagName(bi.builtin))); } else { - // f64 → sqrt (default) const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val; - const sqrt_fn = self.getOrDeclareSqrt(); + const f = self.getOrDeclareMathF64(bi.builtin); var args = [_]c.LLVMValueRef{coerced}; - self.mapRef(c.LLVMBuildCall2(self.builder, self.getSqrtType(), sqrt_fn, &args, 1, "sqrt")); + self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF64Type(), f, &args, 1, @tagName(bi.builtin))); } }, .out => { @@ -1563,6 +1632,7 @@ pub const LLVMEmitter = struct { const field_count: u32 = switch (field_info) { .@"struct" => |s| @intCast(s.fields.len), .@"union" => |u| @intCast(u.fields.len), + .tagged_union => |u| @intCast(u.fields.len), .@"enum" => |e| @intCast(e.variants.len), else => 0, }; @@ -1787,6 +1857,15 @@ pub const LLVMEmitter = struct { return self.cached_i64; } + /// Resolve through pointer types to get the underlying aggregate type. + fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId { + if (!ty.isBuiltin()) { + const info = self.ir_mod.types.get(ty); + if (info == .pointer) return info.pointer.pointee; + } + return ty; + } + // ── Comparison helpers ──────────────────────────────────────────── fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void { @@ -1813,19 +1892,27 @@ pub const LLVMEmitter = struct { } } - // Struct types (strings, slices): compare fields individually + // Struct types (strings, slices, tagged unions): compare fields individually if (kind == c.LLVMStructTypeKind and rhs_kind == c.LLVMStructTypeKind) { const n_fields = c.LLVMCountStructElementTypes(lhs_ty); if (n_fields >= 2) { - // For {ptr, i64} structs (string/slice): compare ptr and len - // eq: (f0_l == f0_r) && (f1_l == f1_r) - // ne: (f0_l != f0_r) || (f1_l != f1_r) const is_eq = (int_pred == c.LLVMIntEQ); const f0_l = c.LLVMBuildExtractValue(self.builder, lhs, 0, "sc.l0"); const f0_r = c.LLVMBuildExtractValue(self.builder, rhs, 0, "sc.r0"); + const cmp0 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f0_l, f0_r, "sc.c0"); + + // Check if field 1 is an array (tagged union payload) — skip comparison + // For tagged unions {tag, [N x i8]}, the tag comparison alone is sufficient + const f1_ty = c.LLVMStructGetTypeAtIndex(lhs_ty, 1); + const f1_kind = c.LLVMGetTypeKind(f1_ty); + if (f1_kind == c.LLVMArrayTypeKind) { + // Tagged union: compare tag only + self.mapRef(cmp0); + return; + } + const f1_l = c.LLVMBuildExtractValue(self.builder, lhs, 1, "sc.l1"); const f1_r = c.LLVMBuildExtractValue(self.builder, rhs, 1, "sc.r1"); - const cmp0 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f0_l, f0_r, "sc.c0"); const cmp1 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f1_l, f1_r, "sc.c1"); const result = if (is_eq) c.LLVMBuildAnd(self.builder, cmp0, cmp1, "sc.and") @@ -1862,6 +1949,8 @@ pub const LLVMEmitter = struct { var rhs = self.resolveRef(bin.rhs); const lhs_ty = c.LLVMTypeOf(lhs); const kind = c.LLVMGetTypeKind(lhs_ty); + // Determine signedness from IR operand type + const is_unsigned = self.isRefUnsigned(bin.lhs) or self.isRefUnsigned(bin.rhs); // Coerce operands to same type if needed if (kind == c.LLVMIntegerTypeKind) { const rhs_ty = c.LLVMTypeOf(rhs); @@ -1869,16 +1958,21 @@ pub const LLVMEmitter = struct { if (rhs_kind == c.LLVMIntegerTypeKind) { const lw = c.LLVMGetIntTypeWidth(lhs_ty); const rw = c.LLVMGetIntTypeWidth(rhs_ty); - if (lw < rw) lhs = c.LLVMBuildSExt(self.builder, lhs, rhs_ty, "cmp.ext") - else if (rw < lw) rhs = c.LLVMBuildSExt(self.builder, rhs, lhs_ty, "cmp.ext"); + if (is_unsigned) { + if (lw < rw) lhs = c.LLVMBuildZExt(self.builder, lhs, rhs_ty, "cmp.ext") + else if (rw < lw) rhs = c.LLVMBuildZExt(self.builder, rhs, lhs_ty, "cmp.ext"); + } else { + if (lw < rw) lhs = c.LLVMBuildSExt(self.builder, lhs, rhs_ty, "cmp.ext") + else if (rw < lw) rhs = c.LLVMBuildSExt(self.builder, rhs, lhs_ty, "cmp.ext"); + } } } const result = if (kind == c.LLVMFloatTypeKind or kind == c.LLVMDoubleTypeKind) c.LLVMBuildFCmp(self.builder, @intCast(float_pred), lhs, rhs, "fcmp") + else if (is_unsigned) + c.LLVMBuildICmp(self.builder, @intCast(unsigned_pred), lhs, rhs, "icmp") else - // Default to signed comparison (most common in sx) c.LLVMBuildICmp(self.builder, @intCast(signed_pred), lhs, rhs, "icmp"); - _ = unsigned_pred; self.mapRef(result); } @@ -2030,24 +2124,36 @@ pub const LLVMEmitter = struct { return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0); } - fn getOrDeclareSqrt(self: *LLVMEmitter) c.LLVMValueRef { - if (c.LLVMGetNamedFunction(self.llvm_module, "sqrt")) |f| return f; - return c.LLVMAddFunction(self.llvm_module, "sqrt", self.getSqrtType()); + fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { + const name: [*:0]const u8 = switch (id) { + .sqrt => "sqrt", + .sin => "sin", + .cos => "cos", + .floor => "floor", + else => unreachable, + }; + if (c.LLVMGetNamedFunction(self.llvm_module, name)) |f| return f; + return c.LLVMAddFunction(self.llvm_module, name, self.getMathF64Type()); } - fn getSqrtType(self: *LLVMEmitter) c.LLVMTypeRef { - // sqrt(f64) → f64 + fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef { var param_types = [_]c.LLVMTypeRef{self.cached_f64}; return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0); } - fn getOrDeclareSqrtf(self: *LLVMEmitter) c.LLVMValueRef { - if (c.LLVMGetNamedFunction(self.llvm_module, "sqrtf")) |f| return f; - return c.LLVMAddFunction(self.llvm_module, "sqrtf", self.getSqrtfType()); + fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef { + const name: [*:0]const u8 = switch (id) { + .sqrt => "sqrtf", + .sin => "sinf", + .cos => "cosf", + .floor => "floorf", + else => unreachable, + }; + if (c.LLVMGetNamedFunction(self.llvm_module, name)) |f| return f; + return c.LLVMAddFunction(self.llvm_module, name, self.getMathF32Type()); } - fn getSqrtfType(self: *LLVMEmitter) c.LLVMTypeRef { - // sqrtf(f32) → f32 + fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef { var param_types = [_]c.LLVMTypeRef{self.cached_f32}; return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0); } @@ -2279,6 +2385,31 @@ pub const LLVMEmitter = struct { } } } + // Struct → Integer (C ABI coercion: store struct to memory, load as integer) + if (val_kind == c.LLVMStructTypeKind and param_kind == c.LLVMIntegerTypeKind) { + const tmp = c.LLVMBuildAlloca(self.builder, param_ty, "abi.tmp"); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(param_ty), tmp); + _ = c.LLVMBuildStore(self.builder, val, tmp); + return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.coerce"); + } + // Integer → Struct (C ABI return coercion: store integer to memory, load as struct) + if (val_kind == c.LLVMIntegerTypeKind and param_kind == c.LLVMStructTypeKind) { + const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "abi.ret.tmp"); + _ = c.LLVMBuildStore(self.builder, val, tmp); + return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.ret.coerce"); + } + // Array → Ptr (array decay: alloca + GEP to first element) + if (val_kind == c.LLVMArrayTypeKind and param_kind == c.LLVMPointerTypeKind) { + const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "ca.arr"); + _ = c.LLVMBuildStore(self.builder, val, tmp); + const zero = c.LLVMConstInt(self.cached_i64, 0, 0); + var indices = [_]c.LLVMValueRef{ zero, zero }; + return c.LLVMBuildGEP2(self.builder, val_ty, tmp, &indices, 2, "ca.decay"); + } + // Int → Ptr (null literal: inttoptr) + if (val_kind == c.LLVMIntegerTypeKind and param_kind == c.LLVMPointerTypeKind) { + return c.LLVMBuildIntToPtr(self.builder, val, param_ty, "ca.itp"); + } return val; } @@ -2390,19 +2521,48 @@ pub const LLVMEmitter = struct { } return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0); }, - .@"enum" => self.cached_i64, // enums are i64 by default + .@"enum" => |e| { + // Use backing type if declared (e.g. enum u32 → i32), else i64 + if (e.backing_type) |bt| return self.toLLVMType(bt); + return self.cached_i64; + }, .@"union" => |u| { - // Union: tag (i64) + largest-field payload - // For simplicity, use { i64, [N x i8] } where N = max field size - var max_size: u32 = 0; + // Untagged union — just [N x i8] + var max_size: usize = 0; for (u.fields) |field| { - const sz = self.ir_mod.types.sizeOf(field.ty); + const sz = self.ir_mod.types.typeSizeBytes(field.ty); if (sz > max_size) max_size = sz; } if (max_size == 0) max_size = 8; + return c.LLVMArrayType2(self.cached_i8, @intCast(max_size)); + }, + .tagged_union => |u| { + // Tagged union — { header, [N x i8] } + var max_size: usize = 0; + for (u.fields) |field| { + const sz = self.ir_mod.types.typeSizeBytes(field.ty); + if (sz > max_size) max_size = sz; + } + if (max_size == 0) max_size = 8; + + var header_size: usize = self.ir_mod.types.typeSizeBytes(u.tag_type); + if (u.backing_type) |bt| { + const bi = self.ir_mod.types.get(bt); + if (bi == .@"struct" and bi.@"struct".fields.len > 1) { + header_size = 0; + const fields = bi.@"struct".fields; + for (fields[0 .. fields.len - 1]) |f| { + header_size += self.ir_mod.types.typeSizeBytes(f.ty); + } + const backing_payload = self.ir_mod.types.typeSizeBytes(fields[fields.len - 1].ty); + if (backing_payload > max_size) max_size = backing_payload; + } + } + + const header_llvm = c.LLVMIntTypeInContext(self.context, @intCast(header_size * 8)); var field_types: [2]c.LLVMTypeRef = .{ - self.cached_i64, // tag - c.LLVMArrayType2(self.cached_i8, max_size), // payload + header_llvm, + c.LLVMArrayType2(self.cached_i8, @intCast(max_size)), }; return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); }, @@ -2423,6 +2583,56 @@ pub const LLVMEmitter = struct { }; } + // ── C ABI coercion for foreign functions ────────────────────────── + // + // On ARM64 (and x86_64), the C calling convention coerces small struct + // arguments to integers for register passing: + // - String/slice {ptr, i64} → ptr (extract raw pointer) + // - Small integer struct (≤ 8 bytes, non-HFA) → i64 + // - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it) + + fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef { + // String/slice → raw pointer + if (ir_ty == .string) return self.cached_ptr; + if (!ir_ty.isBuiltin()) { + const info = self.ir_mod.types.get(ir_ty); + if (info == .slice) return self.cached_ptr; + } + + // Only coerce struct types + if (c.LLVMGetTypeKind(llvm_ty) != c.LLVMStructTypeKind) return llvm_ty; + + // Check if it's an HFA (all float or all double fields) — leave as-is + const n_fields = c.LLVMCountStructElementTypes(llvm_ty); + if (n_fields >= 1 and n_fields <= 4) { + var all_float = true; + var all_double = true; + var fi: c_uint = 0; + while (fi < n_fields) : (fi += 1) { + const ft = c.LLVMStructGetTypeAtIndex(llvm_ty, fi); + const fk = c.LLVMGetTypeKind(ft); + if (fk != c.LLVMFloatTypeKind) all_float = false; + if (fk != c.LLVMDoubleTypeKind) all_double = false; + } + if (all_float or all_double) return llvm_ty; + } + + // Small struct (≤ 8 bytes) → coerce to i64 + const size = c.LLVMABISizeOfType( + c.LLVMGetModuleDataLayout(self.llvm_module), + llvm_ty, + ); + if (size <= 8) return self.cached_i64; + + // Medium struct (9-16 bytes) → coerce to [2 x i64] + if (size <= 16) { + return c.LLVMArrayType2(self.cached_i64, 2); + } + + // Large struct (> 16 bytes) → leave as-is (should be indirect, but handle later) + return llvm_ty; + } + // ── Cached composite types ────────────────────────────────────── fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef { @@ -2457,6 +2667,25 @@ pub const LLVMEmitter = struct { // ── String constant emission ──────────────────────────────────── + /// Build a constant string { ptr, i64 } value without using the builder + /// (safe to call during global initialization, before any function body is emitted). + fn emitConstStringGlobal(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef { + const str_z = self.alloc.dupeZ(u8, str) catch unreachable; + defer self.alloc.free(str_z); + const len: c_uint = @intCast(str.len + 1); // include null terminator + const str_const = c.LLVMConstStringInContext(self.context, str_z.ptr, len - 1, 0); + const arr_ty = c.LLVMArrayType2(self.cached_i8, len); + const str_global_val = c.LLVMAddGlobal(self.llvm_module, arr_ty, "str.data"); + c.LLVMSetInitializer(str_global_val, str_const); + c.LLVMSetGlobalConstant(str_global_val, 1); + c.LLVMSetLinkage(str_global_val, c.LLVMPrivateLinkage); + c.LLVMSetUnnamedAddress(str_global_val, c.LLVMGlobalUnnamedAddr); + // Build constant { ptr, i64 } aggregate + const len_val = c.LLVMConstInt(self.cached_i64, str.len, 0); + var fields = [_]c.LLVMValueRef{ str_global_val, len_val }; + return c.LLVMConstStructInContext(self.context, &fields, 2, 0); + } + fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef { // LLVMBuildGlobalStringPtr needs a null-terminated C string const str_z = self.alloc.dupeZ(u8, str) catch unreachable; @@ -2490,6 +2719,9 @@ pub const LLVMEmitter = struct { .@"union" => |u| { for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable; }, + .tagged_union => |u| { + for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable; + }, .@"enum" => |e| { for (e.variants) |v| name_ids.append(self.alloc, v) catch unreachable; }, @@ -2538,6 +2770,7 @@ pub const LLVMEmitter = struct { const fields = switch (info) { .@"struct" => |s| s.fields, .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, else => &[_]TypeInfo.StructInfo.Field{}, }; @@ -2572,7 +2805,7 @@ pub const LLVMEmitter = struct { var case_values = std.ArrayList(c.LLVMValueRef).empty; defer case_values.deinit(self.alloc); - const is_union = info == .@"union"; + const is_union = info == .@"union" or info == .tagged_union; for (fields, 0..) |field, i| { const case_bb = c.LLVMAppendBasicBlockInContext(self.context, current_func, "fv.case"); c.LLVMAddCase(switch_inst, c.LLVMConstInt(self.cached_i64, @intCast(i), 0), case_bb); @@ -2651,6 +2884,8 @@ pub const LLVMEmitter = struct { if (c.LLVMVerifyModule(self.llvm_module, c.LLVMReturnStatusAction, &err_msg) != 0) { if (err_msg != null) { const msg = std.mem.span(err_msg); + // Dump IR to /tmp for debugging + _ = c.LLVMPrintModuleToFile(self.llvm_module, "/tmp/sx_debug.ll", null); std.debug.print("LLVM verification failed: {s}\n", .{msg}); c.LLVMDisposeMessage(err_msg); } @@ -2707,6 +2942,20 @@ pub const LLVMEmitter = struct { return error.EmitFailed; } } + /// Check if an IR Ref's type is an unsigned integer (u8, u16, u32, u64). + fn isRefUnsigned(self: *LLVMEmitter, ref: Ref) bool { + if (ref.isNone()) return false; + const func = &self.ir_mod.functions.items[self.current_func_idx]; + const ref_idx = ref.index(); + for (func.blocks.items) |*block| { + const first = block.first_ref; + if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) { + const ty = block.insts.items[ref_idx - first].ty; + return ty == .u8 or ty == .u16 or ty == .u32 or ty == .u64; + } + } + return false; + } }; // ── Type classification helpers ───────────────────────────────────── diff --git a/src/ir/inst.zig b/src/ir/inst.zig index 7c7ca13..17023d5 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -251,6 +251,10 @@ pub const Conversion = struct { pub const FieldAccess = struct { base: Ref, field_index: u32, + /// The IR type of the aggregate being accessed (struct, union, etc.). + /// Used by the LLVM emitter to resolve the correct type for GEP operations + /// without guessing from LLVM value chains. + base_type: ?TypeId = null, }; pub const Aggregate = struct { @@ -286,6 +290,9 @@ pub const BuiltinCall = struct { pub const BuiltinId = enum(u16) { out, sqrt, + sin, + cos, + floor, size_of, cast, malloc, diff --git a/src/ir/interp.zig b/src/ir/interp.zig index 747ec58..9b8c5f4 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -800,6 +800,7 @@ pub const Interpreter = struct { const fields = switch (info) { .@"struct" => |s| s.fields, .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, else => return error.CannotEvalComptime, }; if (idx >= fields.len) return error.OutOfBounds; @@ -821,6 +822,7 @@ pub const Interpreter = struct { const fields = switch (info) { .@"struct" => |s| s.fields, .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, else => return error.CannotEvalComptime, }; const field_ty_tag: i64 = if (idx < fields.len) @intFromEnum(fields[idx].ty) else 0; @@ -1225,6 +1227,21 @@ pub const Interpreter = struct { const f = val.asFloat() orelse return error.TypeError; return .{ .value = .{ .float = @sqrt(f) } }; }, + .sin => { + const val = frame.getRef(bi.args[0]); + const f = val.asFloat() orelse return error.TypeError; + return .{ .value = .{ .float = @sin(f) } }; + }, + .cos => { + const val = frame.getRef(bi.args[0]); + const f = val.asFloat() orelse return error.TypeError; + return .{ .value = .{ .float = @cos(f) } }; + }, + .floor => { + const val = frame.getRef(bi.args[0]); + const f = val.asFloat() orelse return error.TypeError; + return .{ .value = .{ .float = @floor(f) } }; + }, .cast, .type_of, .alloc, .dealloc => { return error.CannotEvalComptime; }, diff --git a/src/ir/lower.zig b/src/ir/lower.zig index fb97819..d4e2e73 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -100,6 +100,7 @@ pub const Lowering = struct { protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info + module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2) foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name @@ -109,6 +110,11 @@ pub const Lowering = struct { ty: ?TypeId, // null if no type annotation (inferred) }; + const ModuleConstInfo = struct { + value: *const Node, + ty: TypeId, + }; + const ProtocolDeclInfo = struct { name: []const u8, is_inline: bool, @@ -314,6 +320,31 @@ pub const Lowering = struct { } } // comptime_expr handled in Pass 2 + + // Simple value constants with type annotation (e.g. AF_INET :s32: 2) + if (cd.type_annotation != null) { + switch (cd.value.data) { + .int_literal, .float_literal, .bool_literal, .string_literal, .undef_literal => { + const ty = self.resolveType(cd.type_annotation); + self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = ty }) catch {}; + }, + else => {}, + } + } else { + // Untyped literal constants (e.g. UI_VERT_SRC :: #string GLSL...GLSL;) + switch (cd.value.data) { + .string_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .string }) catch {}, + .int_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .s64 }) catch {}, + .float_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .f64 }) catch {}, + .bool_literal => self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = .bool }) catch {}, + // Complex constant expressions (e.g. COLOR_WHITE :: Color.{ r = 255, ... }) + .struct_literal => { + const inferred_ty = self.inferExprType(cd.value); + self.module_const_map.put(cd.name, .{ .value = cd.value, .ty = inferred_ty }) catch {}; + }, + else => {}, + } + } }, .struct_decl => |sd| { self.registerStructDecl(&sd); @@ -350,6 +381,7 @@ pub const Lowering = struct { const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) { .undef_literal => .zeroinit, .int_literal => |il| .{ .int = il.value }, + .string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) }, else => null, } else null; const gid = self.module.addGlobal(.{ @@ -432,7 +464,7 @@ pub const Lowering = struct { fn lazyLowerFunction(self: *Lowering, name: []const u8) void { // Already lowered? if (self.lowered_functions.contains(name)) return; - // No AST? + // No AST? (builtins, foreign functions, or imported functions not in this file) const fd = self.fn_ast_map.get(name) orelse return; // Check builtin/foreign/generic — these stay as extern stubs if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) return; @@ -457,8 +489,10 @@ pub const Lowering = struct { const saved_scope = self.scope; const saved_defer_base = self.func_defer_base; const saved_block_terminated = self.block_terminated; + const saved_force_block_value = self.force_block_value; self.func_defer_base = self.defer_stack.items.len; self.block_terminated = false; + self.force_block_value = false; // Find the existing extern stub and replace it with a full body const name_id = self.module.types.internString(name); @@ -479,6 +513,7 @@ pub const Lowering = struct { // Restore builder state self.scope = saved_scope; self.func_defer_base = saved_defer_base; + self.force_block_value = saved_force_block_value; self.builder.func = saved_func; self.builder.current_block = saved_block; self.builder.inst_counter = saved_counter; @@ -489,9 +524,21 @@ pub const Lowering = struct { // Re-use the existing function slot — switch builder to it self.builder.func = fid; const func = &self.module.functions.items[@intFromEnum(fid)]; + if (!func.is_extern) { + // Already promoted (e.g., via lowerComptimeDeps) — skip + self.scope = saved_scope; + self.func_defer_base = saved_defer_base; + self.block_terminated = saved_block_terminated; + self.force_block_value = saved_force_block_value; + self.builder.func = saved_func; + self.builder.current_block = saved_block; + self.builder.inst_counter = saved_counter; + return; + } func.is_extern = false; // promote from extern stub to real function func.linkage = if (std.mem.eql(u8, name, "main")) .external else .internal; // Set inst_counter to param count (params occupy refs 0..N-1) + std.debug.assert(func.params.len == fd.params.len); // AST and IR param counts must match self.builder.inst_counter = @intCast(func.params.len); // Create entry block @@ -549,6 +596,7 @@ pub const Lowering = struct { self.scope = saved_scope; self.func_defer_base = saved_defer_base; self.block_terminated = saved_block_terminated; + self.force_block_value = saved_force_block_value; self.builder.func = saved_func; self.builder.current_block = saved_block; self.builder.inst_counter = saved_counter; @@ -947,21 +995,62 @@ pub const Lowering = struct { self.target_type = binding.ty; } } + if (self.target_type == null) { + if (self.global_names.get(asgn.target.data.identifier.name)) |gi| { + self.target_type = gi.ty; + } + } } else if (asgn.target.data == .index_expr) { // For array[i] = val, set target_type to the element type const elem_ty = self.getElementType(self.inferExprType(asgn.target.data.index_expr.object)); if (elem_ty != .void) self.target_type = elem_ty; + } else if (asgn.target.data == .field_access) { + // For obj.field = val, set target_type to the field's type + // Only for enum literals and struct literals — these need target_type to resolve. + // Avoid setting it for complex RHS expressions (calls, casts) because + // resolveCallParamTypes can't override target_type for method-call args. + const needs_target = switch (asgn.value.data) { + .enum_literal, .struct_literal => true, + .call => |vc| vc.callee.data == .enum_literal, + else => false, + }; + if (needs_target) { + const fa = asgn.target.data.field_access; + const obj_ty_raw = self.inferExprType(fa.object); + const obj_ty = if (!obj_ty_raw.isBuiltin()) blk: { + const pinfo = self.module.types.get(obj_ty_raw); + break :blk if (pinfo == .pointer) pinfo.pointer.pointee else obj_ty_raw; + } else obj_ty_raw; + if (!obj_ty.isBuiltin()) { + const field_name_id = self.module.types.internString(fa.field); + const struct_fields = self.getStructFields(obj_ty); + for (struct_fields) |f| { + if (f.name == field_name_id) { + self.target_type = f.ty; + break; + } + } + } + } } const val = self.lowerExpr(asgn.value); self.target_type = old_target; switch (asgn.target.data) { .identifier => |id| { + var handled = false; if (self.scope) |scope| { if (scope.lookup(id.name)) |binding| { if (binding.is_alloca) { + handled = true; if (asgn.op == .assign) { - self.builder.store(binding.ref, val); + // Coerce value to match binding type (e.g., f32 → ?f32, concrete → protocol) + var store_val = val; + const val_ty = self.builder.getRefType(val); + if (val_ty != binding.ty and val_ty != .void and binding.ty != .void) { + store_val = self.coerceToType(val, val_ty, binding.ty); + } + self.builder.store(binding.ref, store_val); } else { // Compound assignment: load, op, store const loaded = self.builder.load(binding.ref, binding.ty); @@ -971,6 +1060,12 @@ pub const Lowering = struct { } } } + // Fallback: global variable assignment + if (!handled) { + if (self.global_names.get(id.name)) |gi| { + self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = val } }, .void); + } + } }, .field_access => |fa| { var obj_ptr = self.lowerExprAsPtr(fa.object); @@ -992,10 +1087,10 @@ pub const Lowering = struct { } else false); if (is_special_container and std.mem.eql(u8, fa.field, "len")) { - const gep = self.builder.structGep(obj_ptr, 1, .s64); + const gep = self.builder.structGepTyped(obj_ptr, 1, .s64, obj_ty); self.storeOrCompound(gep, val, asgn.op, .s64); } else if (is_special_container and std.mem.eql(u8, fa.field, "ptr")) { - const gep = self.builder.structGep(obj_ptr, 0, .s64); + const gep = self.builder.structGepTyped(obj_ptr, 0, .s64, obj_ty); self.storeOrCompound(gep, val, asgn.op, .s64); } else { const field_name_id = self.module.types.internString(fa.field); @@ -1003,11 +1098,15 @@ pub const Lowering = struct { // Check if this is a union field assignment if (!obj_ty.isBuiltin()) { const type_info = self.module.types.get(obj_ty); - if (type_info == .@"union") { - const fields = type_info.@"union".fields; + const union_fields: ?[]const types.TypeInfo.StructInfo.Field = switch (type_info) { + .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, + else => null, + }; + if (union_fields) |fields| { for (fields, 0..) |f, i| { if (f.name == field_name_id) { - const gep = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i) } }, self.module.types.ptrTo(f.ty)); + const gep = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i), .base_type = obj_ty } }, self.module.types.ptrTo(f.ty)); const src_ty = self.inferExprType(asgn.value); const coerced = self.coerceToType(val, src_ty, f.ty); self.storeOrCompound(gep, coerced, asgn.op, f.ty); @@ -1020,8 +1119,8 @@ pub const Lowering = struct { for (fi.@"struct".fields, 0..) |sf, si| { if (sf.name == field_name_id) { // GEP into union payload area, then into the struct field - const union_gep = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i) } }, self.module.types.ptrTo(f.ty)); - const field_gep = self.builder.structGep(union_gep, @intCast(si), sf.ty); + const union_gep = self.builder.emit(.{ .union_gep = .{ .base = obj_ptr, .field_index = @intCast(i), .base_type = obj_ty } }, self.module.types.ptrTo(f.ty)); + const field_gep = self.builder.structGepTyped(union_gep, @intCast(si), sf.ty, f.ty); const src_ty = self.inferExprType(asgn.value); const coerced = self.coerceToType(val, src_ty, sf.ty); self.storeOrCompound(field_gep, coerced, asgn.op, sf.ty); @@ -1044,7 +1143,7 @@ pub const Lowering = struct { break; } } - const gep = self.builder.structGep(obj_ptr, field_idx, field_ty); + const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty); // Coerce value to field type const src_ty = self.inferExprType(asgn.value); const coerced = self.coerceToType(val, src_ty, field_ty); @@ -1064,8 +1163,13 @@ pub const Lowering = struct { // Array alloca: single-index GEP with element stride const gep = self.builder.emit(.{ .index_gep = .{ .lhs = alloca_ref, .rhs = idx } }, ptr_ty); self.storeOrCompound(gep, val, asgn.op, elem_ty); + } else if (is_array) { + // Array in a struct field or other composite: get pointer to array in-place + const obj_ptr = self.lowerExprAsPtr(ie.object); + const gep = self.builder.emit(.{ .index_gep = .{ .lhs = obj_ptr, .rhs = idx } }, ptr_ty); + self.storeOrCompound(gep, val, asgn.op, elem_ty); } else { - // Pointer/slice/complex expression: load and GEP + // Pointer/slice: load the pointer value and GEP const obj = self.lowerExpr(ie.object); const gep = self.builder.emit(.{ .index_gep = .{ .lhs = obj, .rhs = idx } }, ptr_ty); self.storeOrCompound(gep, val, asgn.op, elem_ty); @@ -1132,10 +1236,23 @@ pub const Lowering = struct { const field_name_id = self.module.types.internString(fa.field); for (struct_fields, 0..) |f, i| { if (f.name == field_name_id) { - return self.builder.structGep(obj_ptr, @intCast(i), f.ty); + return self.builder.structGepTyped(obj_ptr, @intCast(i), f.ty, obj_ty); } } - return self.builder.structGep(obj_ptr, 0, .s64); + return self.builder.structGepTyped(obj_ptr, 0, .s64, obj_ty); + }, + .index_expr => |ie| { + const idx = self.lowerExpr(ie.index); + const obj_ty = self.inferExprType(ie.object); + const elem_ty = self.getElementType(obj_ty); + const ptr_ty = self.module.types.ptrTo(elem_ty); + // For fixed-size arrays, use the alloca so GEP addresses the original memory + const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array; + const base = if (is_array) + (self.getExprAlloca(ie.object) orelse self.lowerExprAsPtr(ie.object)) + else + self.lowerExpr(ie.object); + return self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx } }, ptr_ty); }, .deref_expr => |de| { return self.lowerExpr(de.operand); @@ -1218,6 +1335,10 @@ pub const Lowering = struct { if (self.global_names.get(id.name)) |gi| { break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty); } + // Check module-level value constants (e.g. AF_INET :s32: 2) + if (self.module_const_map.get(id.name)) |ci| { + break :blk self.emitModuleConst(ci); + } // Check if it's a function name — produce function pointer reference // Resolve mangled name for block-local functions const eff_fn_name = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name; @@ -1272,6 +1393,30 @@ pub const Lowering = struct { const base = if (is_array) (self.getExprAlloca(ie.object) orelse self.lowerExpr(ie.object)) else self.lowerExpr(ie.object); break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx } }, ptr_ty); } + // address_of(field_access) → emit struct_gep (pointer to field) when object is a pointer + if (uop.op == .address_of and uop.operand.data == .field_access) { + const fa = &uop.operand.data.field_access; + const obj_ty = self.inferExprType(fa.object); + if (!obj_ty.isBuiltin()) { + const ptr_info = self.module.types.get(obj_ty); + if (ptr_info == .pointer) { + const pointee = ptr_info.pointer.pointee; + if (!pointee.isBuiltin()) { + const struct_info = self.module.types.get(pointee); + if (struct_info == .@"struct") { + const field_name_id = self.module.types.internString(fa.field); + for (struct_info.@"struct".fields, 0..) |f, fi| { + if (f.name == field_name_id) { + const obj = self.lowerExpr(fa.object); + const field_ptr_ty = self.module.types.ptrTo(f.ty); + break :blk self.builder.structGepTyped(obj, @intCast(fi), field_ptr_ty, pointee); + } + } + } + } + } + } + } // address_of(identifier) → return alloca directly (pointer to variable) if (uop.op == .address_of and uop.operand.data == .identifier) { const id_name = uop.operand.data.identifier.name; @@ -1418,9 +1563,13 @@ pub const Lowering = struct { // Set target_type from LHS so enum literals on RHS resolve correctly const lhs_ty = self.inferExprType(bop.lhs); const saved_tt = self.target_type; - if (self.target_type == null and lhs_ty != .void and !lhs_ty.isBuiltin()) { - const lhs_info = self.module.types.get(lhs_ty); - if (lhs_info == .@"enum" or lhs_info == .@"union") { + if (lhs_ty != .void) { + if (!lhs_ty.isBuiltin()) { + const lhs_info = self.module.types.get(lhs_ty); + if (lhs_info == .@"enum" or lhs_info == .@"union" or lhs_info == .tagged_union) { + self.target_type = lhs_ty; + } + } else if (lhs_ty == .f32 or lhs_ty == .f64) { self.target_type = lhs_ty; } } @@ -1429,6 +1578,17 @@ pub const Lowering = struct { // Infer result type from LHS operand (covers float, bool, etc.) var ty = lhs_ty; + // Promote int×float → float (e.g., s64 * f32 → f32) + // Only for scalar int LHS — don't affect vectors or structs. + { + const rhs_inferred = self.inferExprType(bop.rhs); + const l_int = isInt(ty); + const r_float = (rhs_inferred == .f32 or rhs_inferred == .f64); + if (l_int and r_float) { + ty = rhs_inferred; + } + } + // Auto-unwrap optional operands for arithmetic/comparison if (!ty.isBuiltin()) { const info = self.module.types.get(ty); @@ -1676,7 +1836,14 @@ pub const Lowering = struct { const is_value = (ie.is_inline or self.force_block_value) and has_else; // Infer result type from then branch for value if-exprs - const result_type: TypeId = if (is_value) self.inferExprType(ie.then_branch) else .void; + // If then_branch is null/void, try else_branch (e.g., `if cond then null else val`) + const result_type: TypeId = if (is_value) blk: { + const then_ty = self.inferExprType(ie.then_branch); + if (then_ty == .void and ie.else_branch != null) { + break :blk self.inferExprType(ie.else_branch.?); + } + break :blk then_ty; + } else .void; const then_bb = self.freshBlock("if.then"); const else_bb: ?BlockId = if (has_else) self.freshBlock("if.else") else null; @@ -1708,6 +1875,9 @@ pub const Lowering = struct { scope.put(bind_name, .{ .ref = slot, .ty = inner_ty, .is_alloca = true }); } } + // Set target_type so null/undef in branches get the right type + const saved_target = self.target_type; + if (is_value and result_type != .void) self.target_type = result_type; if (is_value) { const v = self.lowerExpr(ie.then_branch); if (!self.currentBlockHasTerminator()) { @@ -1735,6 +1905,7 @@ pub const Lowering = struct { } } } + self.target_type = saved_target; // Continue at merge self.builder.switchToBlock(merge_bb); @@ -1969,18 +2140,28 @@ pub const Lowering = struct { .bool_literal => |bl| break :blk @as(u64, if (bl.value) 1 else 0), else => break :blk @as(u64, @intCast(i)), }; - // Look up variant index in the subject's type + // Look up variant value in the subject's type if (!subject_ty.isBuiltin()) { const ty_info = self.module.types.get(subject_ty); - if (ty_info == .@"union") { - for (ty_info.@"union".fields, 0..) |f, vi| { + if (ty_info == .tagged_union) { + for (ty_info.tagged_union.fields, 0..) |f, vi| { const vname = self.module.types.strings.get(f.name); - if (std.mem.eql(u8, vname, pat_name)) break :blk @intCast(vi); + if (std.mem.eql(u8, vname, pat_name)) { + if (ty_info.tagged_union.explicit_tag_values) |vals| { + if (vi < vals.len) break :blk @intCast(@as(u64, @bitCast(vals[vi]))); + } + break :blk @intCast(vi); + } } } else if (ty_info == .@"enum") { for (ty_info.@"enum".variants, 0..) |v, vi| { const vname = self.module.types.strings.get(v); - if (std.mem.eql(u8, vname, pat_name)) break :blk @intCast(vi); + if (std.mem.eql(u8, vname, pat_name)) { + if (ty_info.@"enum".explicit_values) |vals| { + if (vi < vals.len) break :blk @intCast(@as(u64, @bitCast(vals[vi]))); + } + break :blk @intCast(vi); + } } } } @@ -2000,7 +2181,17 @@ pub const Lowering = struct { } // Switch on the subject (for type match, subject IS the tag; for enum match, extract tag) - const tag = if (is_type_match) subject else if (is_optional_match) self.builder.emit(.{ .optional_has_value = .{ .operand = subject } }, .bool) else self.builder.enumTag(subject); + const tag = if (is_type_match) subject else if (is_optional_match) self.builder.emit(.{ .optional_has_value = .{ .operand = subject } }, .bool) else blk: { + // Determine actual tag type from union info (e.g. u32 for SDL_Event) + const tag_ty: TypeId = tt: { + if (!subject_ty.isBuiltin()) { + const ty_info = self.module.types.get(subject_ty); + if (ty_info == .tagged_union) break :tt ty_info.tagged_union.tag_type; + } + break :tt .s32; + }; + break :blk self.builder.enumTag(subject, tag_ty); + }; self.builder.switchBr(tag, cases.items, default_bb.?, &.{}); // Lower each arm's body @@ -2038,8 +2229,8 @@ pub const Lowering = struct { }; if (!subject_ty.isBuiltin()) { const ty_info = self.module.types.get(subject_ty); - if (ty_info == .@"union") { - for (ty_info.@"union".fields, 0..) |f, vi| { + if (ty_info == .tagged_union) { + for (ty_info.tagged_union.fields, 0..) |f, vi| { const vname = self.module.types.strings.get(f.name); if (std.mem.eql(u8, vname, pat_name)) { variant_idx = @intCast(vi); @@ -2107,7 +2298,27 @@ pub const Lowering = struct { self.builder.br(merge_bb, &.{}); } } else { - self.builder.emitUnreachable(); + // For non-exhaustive matches (union/enum with unhandled variants), + // fall through to merge instead of unreachable + const is_exhaustive = blk: { + if (!subject_ty.isBuiltin()) { + const ty_info = self.module.types.get(subject_ty); + if (ty_info == .tagged_union) { + break :blk cases.items.len >= ty_info.tagged_union.fields.len; + } else if (ty_info == .@"enum") { + break :blk cases.items.len >= ty_info.@"enum".variants.len; + } + } + break :blk false; + }; + if (is_exhaustive) { + self.builder.emitUnreachable(); + } else if (is_value and result_type != .void) { + const default_val = self.builder.constUndef(result_type); + self.builder.br(merge_bb, &.{default_val}); + } else { + self.builder.br(merge_bb, &.{}); + } } } } @@ -2144,8 +2355,8 @@ pub const Lowering = struct { const union_ty = self.target_type orelse .s64; if (!union_ty.isBuiltin()) { const union_info = self.module.types.get(union_ty); - if (union_info == .@"union") { - return self.lowerTaggedEnumLiteral(sl, variant_name, union_ty, union_info.@"union"); + if (union_info == .tagged_union) { + return self.lowerTaggedEnumLiteral(sl, variant_name, union_ty, union_info.tagged_union); } } } @@ -2358,6 +2569,17 @@ pub const Lowering = struct { } } } + // Field access: obj.field.method() → GEP to field, pass pointer directly. + // This avoids copying the struct value (mutations through *T must be visible). + if (obj_node.data == .field_access) { + const gep_ref = self.lowerExprAsPtr(obj_node); + // GEP returns a pointer in LLVM but its IR type is the field value type. + // Wrap with addr_of (no-op in LLVM) to set the IR type to *T, + // preventing coerceCallArgs from doing a spurious alloca+store. + const ptr_ty = self.module.types.ptrTo(obj_ty); + method_args.items[0] = self.builder.emit(.{ .addr_of = .{ .operand = gep_ref } }, ptr_ty); + return; + } // General case: alloca+store the value and pass the alloca pointer { const slot = self.builder.alloca(obj_ty); @@ -2416,8 +2638,13 @@ pub const Lowering = struct { // Check union fields + promoted fields if (!ty.isBuiltin()) { const info = self.module.types.get(ty); - if (info == .@"union") { - for (info.@"union".fields) |f| { + const u_fields: ?[]const types.TypeInfo.StructInfo.Field = switch (info) { + .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, + else => null, + }; + if (u_fields) |ufields| { + for (ufields) |f| { if (f.name == field_name_id) return f.ty; // Check promoted fields from anonymous struct variants if (!f.ty.isBuiltin()) { @@ -2567,11 +2794,12 @@ pub const Lowering = struct { fn lowerFieldAccessOnType(self: *Lowering, obj: Ref, obj_ty: TypeId, field: []const u8) Ref { const field_name_id = self.module.types.internString(field); - // Check if it's a union type (tagged enum with payloads) — use enum_payload + // Check if it's a union type if (!obj_ty.isBuiltin()) { const info = self.module.types.get(obj_ty); switch (info) { - .@"union" => |u| { + .tagged_union => |u| { + // Tagged union — use enum_payload for (u.fields, 0..) |f, i| { if (f.name == field_name_id) { return self.builder.emit(.{ .enum_payload = .{ .base = obj, .field_index = @intCast(i) } }, f.ty); @@ -2584,7 +2812,6 @@ pub const Lowering = struct { if (field_info == .@"struct") { for (field_info.@"struct".fields, 0..) |sf, si| { if (sf.name == field_name_id) { - // Reinterpret union data as the struct, then access the field const reinterpreted = self.builder.emit(.{ .union_get = .{ .base = obj, .field_index = 0 } }, f.ty); return self.builder.structGet(reinterpreted, @intCast(si), sf.ty); } @@ -2592,7 +2819,28 @@ pub const Lowering = struct { } } } - // Field not found in union — fall through to struct_get + }, + .@"union" => |u| { + // Untagged union — use union_get to reinterpret bytes + for (u.fields, 0..) |f, i| { + if (f.name == field_name_id) { + return self.builder.emit(.{ .union_get = .{ .base = obj, .field_index = @intCast(i) } }, f.ty); + } + } + // Check promoted fields from anonymous struct variants + for (u.fields) |f| { + if (!f.ty.isBuiltin()) { + const field_info = self.module.types.get(f.ty); + if (field_info == .@"struct") { + for (field_info.@"struct".fields, 0..) |sf, si| { + if (sf.name == field_name_id) { + const reinterpreted = self.builder.emit(.{ .union_get = .{ .base = obj, .field_index = 0 } }, f.ty); + return self.builder.structGet(reinterpreted, @intCast(si), sf.ty); + } + } + } + } + } }, else => {}, } @@ -2673,7 +2921,7 @@ pub const Lowering = struct { sl: *const ast.StructLiteral, variant_name: []const u8, union_ty: TypeId, - union_info: types.TypeInfo.UnionInfo, + union_info: types.TypeInfo.TaggedUnionInfo, ) Ref { const tag = self.resolveVariantValue(union_ty, variant_name); const name_id = self.module.types.internString(variant_name); @@ -2743,9 +2991,14 @@ pub const Lowering = struct { } } }, - .@"union" => |u| { + .tagged_union => |u| { for (u.fields, 0..) |f, i| { - if (f.name == name_id) return @intCast(i); + if (f.name == name_id) { + if (u.explicit_tag_values) |vals| { + if (i < vals.len) return @intCast(@as(u64, @bitCast(vals[i]))); + } + return @intCast(i); + } } }, else => {}, @@ -2759,7 +3012,7 @@ pub const Lowering = struct { const info = self.module.types.get(ty); const name_id = self.module.types.internString(variant_name); switch (info) { - .@"union" => |u| { + .tagged_union => |u| { for (u.fields, 0..) |f, i| { if (f.name == name_id) return @intCast(i); } @@ -3051,10 +3304,10 @@ pub const Lowering = struct { const target = self.target_type orelse .s64; if (!target.isBuiltin()) { const info = self.module.types.get(target); - if (info == .@"union") { + if (info == .tagged_union) { const tag = self.resolveVariantIndex(target, c.callee.data.enum_literal.name); - if (tag < info.@"union".fields.len) { - enum_payload_ty = info.@"union".fields[tag].ty; + if (tag < info.tagged_union.fields.len) { + enum_payload_ty = info.tagged_union.fields[tag].ty; } } } @@ -3067,6 +3320,28 @@ pub const Lowering = struct { if (enum_payload_ty) |ept| { if (ai == 0) self.target_type = ept; } + // Implicit address-of: when param expects *T and arg is an identifier + // with an alloca of type T, pass the alloca pointer directly (reference + // semantics, so mutations through the pointer are visible to the caller). + if (ai < param_types.len and arg.data == .identifier) { + const pt = param_types[ai]; + if (!pt.isBuiltin()) { + const pti = self.module.types.get(pt); + if (pti == .pointer) { + if (self.scope) |scope| { + if (scope.lookup(arg.data.identifier.name)) |binding| { + // Only apply when the binding type matches the pointee type + if (binding.is_alloca and binding.ty == pti.pointer.pointee) { + const ptr_ty = self.module.types.ptrTo(binding.ty); + args.append(self.alloc, self.builder.emit(.{ .addr_of = .{ .operand = binding.ref } }, ptr_ty)) catch unreachable; + self.target_type = saved_target; + continue; + } + } + } + } + } + } const val = self.lowerExpr(arg); self.target_type = saved_target; args.append(self.alloc, val) catch unreachable; @@ -3121,9 +3396,16 @@ pub const Lowering = struct { if (resolveBuiltin(id.name)) |bid| { const ret_ty: TypeId = switch (bid) { .malloc => .s64, // pointer - .sqrt => .f64, .size_of => .s64, .memcpy, .memset => .s64, + .sqrt, .sin, .cos, .floor => blk: { + // Math builtins: return type matches argument type ($T -> T) + if (c.args.len > 0) { + const arg_ty = self.inferExprType(c.args[0]); + if (arg_ty == .f32) break :blk TypeId.f32; + } + break :blk TypeId.f64; + }, else => .void, }; return self.builder.callBuiltin(bid, args.items, ret_ty); @@ -3175,7 +3457,52 @@ pub const Lowering = struct { if (scope.lookup(id.name)) |binding| { const callee_ref = if (binding.is_alloca) self.builder.load(binding.ref, binding.ty) else binding.ref; const owned = self.alloc.dupe(Ref, args.items) catch unreachable; - return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, .s64); + const ret_ty = if (!binding.ty.isBuiltin()) blk: { + const bti = self.module.types.get(binding.ty); + break :blk if (bti == .function) bti.function.ret else .s64; + } else .s64; + return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, ret_ty); + } + } + // May be a global variable holding a function pointer + if (self.global_names.get(id.name)) |gi| { + if (!gi.ty.isBuiltin()) { + const gti = self.module.types.get(gi.ty); + if (gti == .function) { + const callee_ref = self.builder.emit(.{ .global_get = gi.id }, gi.ty); + // Coerce args to match fn-ptr param types (including implicit address-of) + for (args.items, 0..) |*arg, ai| { + if (ai < gti.function.params.len) { + const dst_ty = gti.function.params[ai]; + const src_ty = self.inferExprType(c.args[ai]); + // Implicit address-of: passing T where *T expected + if (!dst_ty.isBuiltin()) { + const dti = self.module.types.get(dst_ty); + if (dti == .pointer and dti.pointer.pointee == src_ty and src_ty != .void) { + // For identifier args, pass the alloca directly (reference semantics) + if (c.args[ai].data == .identifier) { + if (self.scope) |scope| { + if (scope.lookup(c.args[ai].data.identifier.name)) |binding| { + if (binding.is_alloca) { + arg.* = self.builder.emit(.{ .addr_of = .{ .operand = binding.ref } }, dst_ty); + continue; + } + } + } + } + // For other expressions, copy semantics + const slot = self.builder.alloca(src_ty); + self.builder.store(slot, arg.*); + arg.* = slot; + continue; + } + } + arg.* = self.coerceToType(arg.*, src_ty, dst_ty); + } + } + const owned = self.alloc.dupe(Ref, args.items) catch unreachable; + return self.builder.emit(.{ .call_indirect = .{ .callee = callee_ref, .args = owned } }, gti.function.ret); + } } } // Unresolved — emit placeholder @@ -3196,12 +3523,12 @@ pub const Lowering = struct { // Try instantiate as type function if (self.instantiateTypeFunction(inner_name, inner_name, fd, inner_call.args)) |result_ty| { const type_info = self.module.types.get(result_ty); - if (type_info == .@"union") { + if (type_info == .tagged_union) { // Qualified enum construction: Type.variant(payload) const tag = self.resolveVariantIndex(result_ty, fa.field); var payload = if (args.items.len > 0) args.items[0] else Ref.none; if (!payload.isNone()) { - const fields = type_info.@"union".fields; + const fields = type_info.tagged_union.fields; if (tag < fields.len) { const field_ty = fields[tag].ty; if (field_ty != .void) { @@ -3233,10 +3560,14 @@ pub const Lowering = struct { else => null, }; if (obj_name) |name| { - // Not a variable in scope → namespace prefix + // Check local scope first if (self.scope) |scope| { - if (scope.lookup(name) == null) break :blk true; - } else break :blk true; + if (scope.lookup(name) != null) break :blk false; + } + // Check global variables (e.g., g_font : *FontAtlas) + if (self.global_names.contains(name)) break :blk false; + // Not a local or global variable → namespace prefix + break :blk true; } break :blk false; }; @@ -3280,12 +3611,12 @@ pub const Lowering = struct { const type_name_id = self.module.types.internString(type_name); if (self.module.types.findByName(type_name_id)) |union_ty| { const type_info = self.module.types.get(union_ty); - if (type_info == .@"union") { + if (type_info == .tagged_union) { const tag = self.resolveVariantIndex(union_ty, func_name); var payload = if (args.items.len > 0) args.items[0] else Ref.none; // Coerce payload to match field type if (!payload.isNone()) { - const fields = type_info.@"union".fields; + const fields = type_info.tagged_union.fields; if (tag < fields.len) { const field_ty = fields[tag].ty; const payload_ty = self.inferExprType(c.args[0]); @@ -3420,8 +3751,8 @@ pub const Lowering = struct { // Coerce payload to match the field type if (!payload.isNone() and !target.isBuiltin()) { const info = self.module.types.get(target); - if (info == .@"union") { - const fields = info.@"union".fields; + if (info == .tagged_union) { + const fields = info.tagged_union.fields; if (tag < fields.len) { const field_ty = fields[tag].ty; const payload_ty = self.inferExprType(c.args[0]); @@ -3481,6 +3812,9 @@ pub const Lowering = struct { // Note: "print" is NOT here — it's a comptime-expanded function, not a simple builtin .{ "out", inst_mod.BuiltinId.out }, .{ "sqrt", inst_mod.BuiltinId.sqrt }, + .{ "sin", inst_mod.BuiltinId.sin }, + .{ "cos", inst_mod.BuiltinId.cos }, + .{ "floor", inst_mod.BuiltinId.floor }, .{ "size_of", inst_mod.BuiltinId.size_of }, .{ "cast", inst_mod.BuiltinId.cast }, .{ "malloc", inst_mod.BuiltinId.malloc }, @@ -3643,7 +3977,7 @@ pub const Lowering = struct { for (capture_list, 0..) |cap, i| { // GEP into env struct to get field pointer - const field_ptr = self.builder.structGep(env_local, @intCast(i), self.module.types.ptrTo(cap.ty)); + const field_ptr = self.builder.structGepTyped(env_local, @intCast(i), self.module.types.ptrTo(cap.ty), env_struct_ty); // Load the captured value into a local alloca const loaded = self.builder.load(field_ptr, cap.ty); const slot = self.builder.alloca(cap.ty); @@ -3710,7 +4044,7 @@ pub const Lowering = struct { // Store captured values into env struct fields for (capture_list, 0..) |cap, i| { - const gep = self.builder.structGep(env_local, @intCast(i), self.module.types.ptrTo(cap.ty)); + const gep = self.builder.structGepTyped(env_local, @intCast(i), self.module.types.ptrTo(cap.ty), env_struct_ty); const val = if (cap.is_alloca) self.builder.load(cap.ref, cap.ty) else @@ -3933,110 +4267,11 @@ pub const Lowering = struct { /// Byte size of an IR type matching LLVM's type layout. fn typeSizeBytes(self: *Lowering, ty: TypeId) usize { - if (ty == .void) return 0; - if (ty == .bool) return 1; - if (ty == .u8 or ty == .s8) return 1; - if (ty == .u16 or ty == .s16) return 2; - if (ty == .s32 or ty == .u32 or ty == .f32) return 4; - if (ty == .s64 or ty == .u64 or ty == .f64) return 8; - if (ty == .string) return 16; // {ptr, i64} - if (ty.isBuiltin()) return 8; // default for unknown builtins - const info = self.module.types.get(ty); - return switch (info) { - .pointer, .many_pointer, .function => 8, - .slice => 16, // {ptr, i64} - .closure => 16, // {fn_ptr, env_ptr} - .optional => |o| blk: { - // LLVM optional is { T, i1 } — compute struct size with alignment - const child_info = self.module.types.get(o.child); - // ?*T / ?fn / ?closure → bare pointer (8 bytes) - if (child_info == .pointer or child_info == .many_pointer or child_info == .function) - break :blk 8; - if (child_info == .closure) - break :blk 16; // {fn_ptr, env_ptr} - const cs = self.typeSizeBytes(o.child); - const ca = self.typeAlignBytes(o.child); - // { T, i1 } — i1 goes right after T, then pad to struct alignment - const unpadded = cs + 1; - break :blk (unpadded + ca - 1) & ~(ca - 1); - }, - .@"struct" => |s| blk: { - var offset: usize = 0; - var max_a: usize = 1; - for (s.fields) |f| { - const fs = self.typeSizeBytes(f.ty); - const fa = self.typeAlignBytes(f.ty); - if (fa > max_a) max_a = fa; - offset = (offset + fa - 1) & ~(fa - 1); - offset += fs; - } - // Pad to struct alignment - break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1); - }, - .@"union" => |u| blk: { - var max_payload: usize = 0; - for (u.fields) |f| { - const fs = self.typeSizeBytes(f.ty); - if (fs > max_payload) max_payload = fs; - } - // Tagged union: { i64_tag, [max_payload x i8] }, padded to 8-byte alignment - const raw = max_payload + 8; - break :blk (raw + 7) & ~@as(usize, 7); - }, - .array => |a| blk: { - const elem_size = self.typeSizeBytes(a.element); - break :blk elem_size * @as(usize, @intCast(a.length)); - }, - .vector => |v| blk: { - const elem_size = self.typeSizeBytes(v.element); - break :blk elem_size * @as(usize, @intCast(v.length)); - }, - .tuple => |t| blk: { - var offset: usize = 0; - var max_a: usize = 1; - for (t.fields) |f| { - const fs = self.typeSizeBytes(f); - const fa = self.typeAlignBytes(f); - if (fa > max_a) max_a = fa; - offset = (offset + fa - 1) & ~(fa - 1); - offset += fs; - } - break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1); - }, - else => 8, - }; + return self.module.types.typeSizeBytes(ty); } - /// Natural alignment of an IR type (matches LLVM's ABI alignment). fn typeAlignBytes(self: *Lowering, ty: TypeId) usize { - if (ty == .void) return 1; - if (ty == .bool) return 1; - if (ty == .u8 or ty == .s8) return 1; - if (ty == .u16 or ty == .s16) return 2; - if (ty == .s32 or ty == .u32 or ty == .f32) return 4; - if (ty == .s64 or ty == .u64 or ty == .f64) return 8; - if (ty == .string) return 8; // {ptr, i64} - if (ty.isBuiltin()) return 8; - const info = self.module.types.get(ty); - return switch (info) { - .pointer, .many_pointer, .function => 8, - .slice, .closure => 8, - .optional => |o| blk: { - const child_info = self.module.types.get(o.child); - if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure) - break :blk 8; - break :blk self.typeAlignBytes(o.child); - }, - .@"struct" => |s| blk: { - var max_a: usize = 1; - for (s.fields) |f| { - const fa = self.typeAlignBytes(f.ty); - if (fa > max_a) max_a = fa; - } - break :blk max_a; - }, - else => 8, - }; + return self.module.types.typeAlignBytes(ty); } fn resolveReturnType2(self: *Lowering, rt: ?*const Node) TypeId { @@ -4181,7 +4416,7 @@ pub const Lowering = struct { break; } } - const gep = self.builder.structGep(obj_ptr, field_idx, field_ty); + const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty); self.builder.store(gep, val); }, .deref_expr => |de| { @@ -4229,7 +4464,7 @@ pub const Lowering = struct { /// Creates a comptime function and emits a `call` to it, so the /// interpreter can evaluate it and replace with the constant result. fn lowerInlineComptime(self: *Lowering, expr: *const Node) Ref { - const ret_ty: TypeId = .s64; // stub — real type inferred later + const ret_ty: TypeId = self.target_type orelse self.inferExprType(expr); const func_id = self.createComptimeFunction("__ct", expr, ret_ty); // Emit a call to the comptime function. At interpretation time, // this will be evaluated and the result inlined as a constant. @@ -4441,9 +4676,9 @@ pub const Lowering = struct { const zero_len = self.builder.constInt(0, .s64); const slice_slot = self.builder.alloca(any_slice_ty); // Store ptr (field 0) and len (field 1) into the slice alloca - const ptr_gep = self.builder.structGep(slice_slot, 0, self.module.types.ptrTo(.any)); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(.any), any_slice_ty); self.builder.store(ptr_gep, null_ptr); - const len_gep = self.builder.structGep(slice_slot, 1, .s64); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty); self.builder.store(len_gep, zero_len); if (self.scope) |scope| { scope.put(param_name, .{ .ref = slice_slot, .ty = any_slice_ty, .is_alloca = true }); @@ -4509,9 +4744,9 @@ pub const Lowering = struct { const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, self.module.types.ptrTo(.any)); const len_ref = self.builder.constInt(@intCast(n), .s64); // Store into slice fields - const ptr_gep = self.builder.structGep(slice_slot, 0, self.module.types.ptrTo(.any)); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(.any), any_slice_ty); self.builder.store(ptr_gep, data_ptr); - const len_gep = self.builder.structGep(slice_slot, 1, .s64); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty); self.builder.store(len_gep, len_ref); if (self.scope) |scope| { @@ -4565,9 +4800,9 @@ pub const Lowering = struct { const null_ptr = self.builder.constNull(self.module.types.ptrTo(elem_ty)); const zero_len = self.builder.constInt(0, .s64); const slice_slot = self.builder.alloca(slice_ty); - const ptr_gep = self.builder.structGep(slice_slot, 0, self.module.types.ptrTo(elem_ty)); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(elem_ty), slice_ty); self.builder.store(ptr_gep, null_ptr); - const len_gep = self.builder.structGep(slice_slot, 1, .s64); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty); self.builder.store(len_gep, zero_len); const slice_val = self.builder.load(slice_slot, slice_ty); // Replace args: keep fixed args, append slice @@ -4636,9 +4871,9 @@ pub const Lowering = struct { const zero = self.builder.constInt(0, .s64); const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, self.module.types.ptrTo(array_elem)); const len_ref = self.builder.constInt(@intCast(variadic_count), .s64); - const ptr_gep = self.builder.structGep(slice_slot, 0, self.module.types.ptrTo(array_elem)); + const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(array_elem), slice_ty); self.builder.store(ptr_gep, data_ptr); - const len_gep = self.builder.structGep(slice_slot, 1, .s64); + const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty); self.builder.store(len_gep, len_ref); const slice_val = self.builder.load(slice_slot, slice_ty); @@ -5169,6 +5404,7 @@ pub const Lowering = struct { const count: i64 = switch (info) { .@"struct" => |s| @intCast(s.fields.len), .@"union" => |u| @intCast(u.fields.len), + .tagged_union => |u| @intCast(u.fields.len), .@"enum" => |e| @intCast(e.variants.len), .array => |a| @intCast(a.length), .vector => |v| @intCast(v.length), @@ -5324,6 +5560,7 @@ pub const Lowering = struct { return switch (info) { .@"struct" => |s| self.module.types.getString(s.name), .@"union" => |u| self.module.types.getString(u.name), + .tagged_union => |u| self.module.types.getString(u.name), .@"enum" => |e| self.module.types.getString(e.name), .pointer => |p| blk: { const inner = self.formatTypeName(p.pointee); @@ -5487,6 +5724,7 @@ pub const Lowering = struct { return switch (info) { .@"struct" => |s| self.module.types.getString(s.name), .@"union" => |u| self.module.types.getString(u.name), + .tagged_union => |u| self.module.types.getString(u.name), .@"enum" => |e| self.module.types.getString(e.name), .pointer => |p| blk: { const inner = self.mangleTypeName(p.pointee); @@ -5578,8 +5816,8 @@ pub const Lowering = struct { for (self.module.types.infos.items, 0..) |info, idx| { const matches = switch (c) { .@"struct" => info == .@"struct", - .@"enum" => info == .@"enum" or info == .@"union", - .@"union" => info == .@"union", + .@"enum" => info == .@"enum" or info == .tagged_union, + .@"union" => info == .@"union" or info == .tagged_union, .slice => info == .slice, .array => info == .array, .pointer => info == .pointer or info == .many_pointer, @@ -5673,6 +5911,28 @@ pub const Lowering = struct { return types_list.items; } + // Check global function pointer variables + if (self.global_names.get(bare_name)) |gi| { + if (!gi.ty.isBuiltin()) { + const ti = self.module.types.get(gi.ty); + if (ti == .function) { + return ti.function.params; + } + } + } + + // Check local scope for function pointer variables + if (self.scope) |scope| { + if (scope.lookup(bare_name)) |binding| { + if (!binding.ty.isBuiltin()) { + const ti = self.module.types.get(binding.ty); + if (ti == .function) { + return ti.function.params; + } + } + } + } + return &.{}; } @@ -5855,6 +6115,32 @@ pub const Lowering = struct { if (node.data == .call) { return self.resolveTypeCallWithBindings(&node.data.call); } + // Handle compound types that may contain generic structs (e.g., *List(ViewChild)) + // These need the lowerer's resolveType to properly instantiate generics. + switch (node.data) { + .pointer_type_expr => |pt| { + const pointee = self.resolveTypeWithBindings(pt.pointee_type); + return self.module.types.ptrTo(pointee); + }, + .slice_type_expr => |st| { + const elem = self.resolveTypeWithBindings(st.element_type); + return self.module.types.sliceOf(elem); + }, + .many_pointer_type_expr => |mp| { + const elem = self.resolveTypeWithBindings(mp.element_type); + return self.module.types.manyPtrTo(elem); + }, + .optional_type_expr => |ot| { + const child = self.resolveTypeWithBindings(ot.inner_type); + return self.module.types.optionalOf(child); + }, + .array_type_expr => |at| { + const elem = self.resolveTypeWithBindings(at.element_type); + const len: u32 = if (at.length.data == .int_literal) @intCast(at.length.data.int_literal.value) else 0; + return self.module.types.arrayOf(elem, len); + }, + else => {}, + } // Check type aliases before falling through to type_bridge if (node.data == .type_expr) { if (self.type_alias_map.get(node.data.type_expr.name)) |alias_ty| return alias_ty; @@ -6074,7 +6360,7 @@ pub const Lowering = struct { const mangled_name_id = table.internString(mangled_name); if (table.findByName(mangled_name_id)) |existing| { const info = table.get(existing); - if ((info == .@"struct" and info.@"struct".fields.len > 0) or info == .@"union") { + if ((info == .@"struct" and info.@"struct".fields.len > 0) or info == .@"union" or info == .tagged_union) { return existing; } } @@ -6158,10 +6444,10 @@ pub const Lowering = struct { } const alias_name_id = table.internString(alias_name); - const info: types.TypeInfo = .{ .@"union" = .{ + const info: types.TypeInfo = .{ .tagged_union = .{ .name = alias_name_id, .fields = variant_fields.items, - .tag_type = null, + .tag_type = .s64, } }; const id = if (table.findByName(alias_name_id)) |existing| existing else table.intern(info); table.update(id, info); @@ -6169,10 +6455,10 @@ pub const Lowering = struct { // Also register under mangled name if (!std.mem.eql(u8, alias_name, mangled_name)) { const mangled_name_id = table.internString(mangled_name); - const mangled_info: types.TypeInfo = .{ .@"union" = .{ + const mangled_info: types.TypeInfo = .{ .tagged_union = .{ .name = mangled_name_id, .fields = variant_fields.items, - .tag_type = null, + .tag_type = .s64, } }; const mid = if (table.findByName(mangled_name_id)) |existing| existing else table.intern(mangled_info); table.update(mid, mangled_info); @@ -6292,7 +6578,7 @@ pub const Lowering = struct { using_idx += 1; } if (field_idx < total_explicit) { - const field_ty = type_bridge.resolveAstType(sd.field_types[field_idx], table); + const field_ty = self.resolveType(sd.field_types[field_idx]); fields.append(self.alloc, .{ .name = table.internString(sd.field_names[field_idx]), .ty = field_ty, @@ -6371,6 +6657,13 @@ pub const Lowering = struct { const ti = table.get(ty); switch (ti) { .@"union" => |u| { + const old_name = table.getString(u.name); + if (!std.mem.eql(u8, old_name, "__anon")) return; + const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ parent_name, field_name }) catch return; + const qname_id = table.internString(qualified); + table.update(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields } }); + }, + .tagged_union => |u| { const old_name = table.getString(u.name); if (!std.mem.eql(u8, old_name, "__anon")) return; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ parent_name, field_name }) catch return; @@ -6390,7 +6683,7 @@ pub const Lowering = struct { } } } - table.update(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields, .tag_type = u.tag_type } }); + table.update(ty, .{ .tagged_union = .{ .name = qname_id, .fields = u.fields, .tag_type = u.tag_type, .backing_type = u.backing_type, .explicit_tag_values = u.explicit_tag_values } }); }, .@"enum" => |e| { const old_name = table.getString(e.name); @@ -6857,7 +7150,13 @@ pub const Lowering = struct { }; if (resolveBuiltin(bare_name)) |bid| { return switch (bid) { - .sqrt => .f64, + .sqrt, .sin, .cos, .floor => blk: { + if (c.args.len > 0) { + const arg_ty = self.inferExprType(c.args[0]); + if (arg_ty == .f32) break :blk TypeId.f32; + } + break :blk TypeId.f64; + }, .size_of, .malloc => .s64, .cast => if (c.args.len > 0) self.resolveTypeArg(c.args[0]) else .s64, else => .s64, @@ -6885,14 +7184,32 @@ pub const Lowering = struct { } else if (c.callee.data == .field_access) { const cfa = c.callee.data.field_access; // Check if receiver is a protocol type → return protocol method type + const recv_ty = self.inferExprType(cfa.object); { - const recv_ty = self.inferExprType(cfa.object); if (self.getProtocolInfo(recv_ty)) |proto_info| { for (proto_info.methods) |m| { if (std.mem.eql(u8, m.name, cfa.field)) return m.ret_type; } } } + // Instance method call: obj.method(args) → look up StructName.method + { + var obj_ty = recv_ty; + if (!obj_ty.isBuiltin()) { + const oi = self.module.types.get(obj_ty); + if (oi == .pointer) obj_ty = oi.pointer.pointee; + } + if (!obj_ty.isBuiltin()) { + const oi = self.module.types.get(obj_ty); + if (oi == .@"struct") { + const struct_name = self.module.types.getString(oi.@"struct".name); + const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, cfa.field }) catch cfa.field; + if (self.resolveFuncByName(qualified)) |fid| { + return self.module.functions.items[@intFromEnum(fid)].ret; + } + } + } + } // Type.variant(args) — qualified enum construction const type_name = switch (cfa.object.data) { .identifier => |id| id.name, @@ -6903,7 +7220,7 @@ pub const Lowering = struct { const type_name_id = self.module.types.internString(tn); if (self.module.types.findByName(type_name_id)) |ty| { const ti = self.module.types.get(ty); - if (ti == .@"union" or ti == .@"enum") return ty; + if (ti == .tagged_union or ti == .@"enum") return ty; } // Check for qualified function call const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tn, cfa.field }) catch cfa.field; @@ -6919,6 +7236,13 @@ pub const Lowering = struct { }, .field_access => |fa| { var obj_ty = self.inferExprType(fa.object); + // Auto-deref: if object is a pointer, resolve through it (matches lowerFieldAccess behavior) + if (!obj_ty.isBuiltin()) { + const ptr_info = self.module.types.get(obj_ty); + if (ptr_info == .pointer) { + obj_ty = ptr_info.pointer.pointee; + } + } // Optional chaining: ?T.field → ?FieldType (flattened if field is already optional) const is_opt_chain = fa.is_optional; if (is_opt_chain and !obj_ty.isBuiltin()) { @@ -6938,22 +7262,24 @@ pub const Lowering = struct { const field_name_id = self.module.types.internString(fa.field); // Check union fields (tagged enum payloads) + promoted struct fields const info = self.module.types.get(obj_ty); - switch (info) { - .@"union" => |u| { - for (u.fields) |f| { - if (f.name == field_name_id) return if (is_opt_chain) self.optionalOfFlattened(f.ty) else f.ty; - // Check promoted fields from anonymous struct variants - if (!f.ty.isBuiltin()) { - const fi = self.module.types.get(f.ty); - if (fi == .@"struct") { - for (fi.@"struct".fields) |sf| { - if (sf.name == field_name_id) return if (is_opt_chain) self.optionalOfFlattened(sf.ty) else sf.ty; - } + const u_fields2: ?[]const types.TypeInfo.StructInfo.Field = switch (info) { + .@"union" => |u| u.fields, + .tagged_union => |u| u.fields, + else => null, + }; + if (u_fields2) |ufields| { + for (ufields) |f| { + if (f.name == field_name_id) return if (is_opt_chain) self.optionalOfFlattened(f.ty) else f.ty; + // Check promoted fields from anonymous struct variants + if (!f.ty.isBuiltin()) { + const fi = self.module.types.get(f.ty); + if (fi == .@"struct") { + for (fi.@"struct".fields) |sf| { + if (sf.name == field_name_id) return if (is_opt_chain) self.optionalOfFlattened(sf.ty) else sf.ty; } } } - }, - else => {}, + } } // Check vector element access (.x/.y/.z/.w) if (info == .vector) { @@ -6974,6 +7300,14 @@ pub const Lowering = struct { return binding.ty; } } + // Check global variables (e.g., `context : Context`) + if (self.global_names.get(id.name)) |gi| { + return gi.ty; + } + // Check module-level value constants (e.g., WIDTH :f32: 800) + if (self.module_const_map.get(id.name)) |ci| { + return ci.ty; + } return .s64; }, .type_expr => |te| { @@ -7311,6 +7645,7 @@ pub const Lowering = struct { const info = self.module.types.get(ty); return switch (info) { .pointer, .tuple, .optional => self.builder.constNull(ty), + .@"struct", .array, .slice, .many_pointer => self.builder.constNull(ty), else => self.builder.constUndef(ty), }; } @@ -7361,6 +7696,34 @@ pub const Lowering = struct { self.builder.emitVoid(.{ .global_set = .{ .global = ctx_gi.id, .value = ctx_val } }, .void); } + fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo) Ref { + switch (ci.value.data) { + .int_literal => |lit| { + // If declared type is float, convert integer value to float constant + if (ci.ty == .f32 or ci.ty == .f64) { + return self.builder.constFloat(@floatFromInt(lit.value), ci.ty); + } + return self.builder.constInt(lit.value, ci.ty); + }, + .float_literal => |lit| return self.builder.constFloat(lit.value, ci.ty), + .bool_literal => |lit| return self.builder.emit(.{ .const_bool = lit.value }, .bool), + .string_literal => |lit| { + const str = if (lit.is_raw) lit.raw else unescape.unescapeString(self.alloc, lit.raw) catch lit.raw; + const sid = self.module.types.internString(str); + return self.builder.constString(sid); + }, + .undef_literal => return self.builder.constUndef(ci.ty), + else => { + // Complex expressions (struct_literal, call, etc.) — lower on demand + const saved_target = self.target_type; + self.target_type = ci.ty; + const result = self.lowerExpr(ci.value); + self.target_type = saved_target; + return result; + }, + } + } + fn emitPlaceholder(self: *Lowering, name: []const u8) Ref { const sid = self.module.types.internString(name); return self.builder.emit(.{ .placeholder = sid }, .s64); @@ -7533,7 +7896,28 @@ pub const Lowering = struct { fn coerceCallArgs(self: *Lowering, args: []Ref, params: []const Function.Param) void { for (0..@min(args.len, params.len)) |i| { const src_ty = self.builder.getRefType(args[i]); - args[i] = self.coerceToType(args[i], src_ty, params[i].ty); + const dst_ty = params[i].ty; + if (!src_ty.isBuiltin() and !dst_ty.isBuiltin()) { + const src_info = self.module.types.get(src_ty); + const dst_info = self.module.types.get(dst_ty); + // Array → many_pointer decay: alloca the array, GEP to first element + if (src_info == .array and dst_info == .many_pointer) { + const slot = self.builder.alloca(src_ty); + self.builder.store(slot, args[i]); + const zero = self.builder.constInt(0, .s64); + args[i] = self.builder.emit(.{ .index_gep = .{ .lhs = slot, .rhs = zero } }, dst_ty); + continue; + } + // Implicit address-of: passing T value where *T is expected → alloca + store + // Only when the pointee type matches the source type. + if (dst_info == .pointer and src_info != .pointer and dst_info.pointer.pointee == src_ty) { + const slot = self.builder.alloca(src_ty); + self.builder.store(slot, args[i]); + args[i] = slot; + continue; + } + } + args[i] = self.coerceToType(args[i], src_ty, dst_ty); } } diff --git a/src/ir/module.zig b/src/ir/module.zig index 872a3ed..e4f2e27 100644 --- a/src/ir/module.zig +++ b/src/ir/module.zig @@ -313,14 +313,18 @@ pub const Builder = struct { return self.emit(.{ .struct_gep = .{ .base = base, .field_index = field_index } }, ty); } + pub fn structGepTyped(self: *Builder, base: Ref, field_index: u32, ty: TypeId, base_type: TypeId) Ref { + return self.emit(.{ .struct_gep = .{ .base = base, .field_index = field_index, .base_type = base_type } }, ty); + } + // ── Enum ops ──────────────────────────────────────────────────── pub fn enumInit(self: *Builder, tag: u32, payload: Ref, ty: TypeId) Ref { return self.emit(.{ .enum_init = .{ .tag = tag, .payload = payload } }, ty); } - pub fn enumTag(self: *Builder, val: Ref) Ref { - return self.emit(.{ .enum_tag = .{ .operand = val } }, .s32); + pub fn enumTag(self: *Builder, val: Ref, tag_ty: TypeId) Ref { + return self.emit(.{ .enum_tag = .{ .operand = val } }, tag_ty); } // ── Optional ops ──────────────────────────────────────────────── diff --git a/src/ir/print.zig b/src/ir/print.zig index ff9f0fc..7697026 100644 --- a/src/ir/print.zig +++ b/src/ir/print.zig @@ -454,6 +454,7 @@ fn writeType(id: TypeId, tt: *const TypeTable, writer: Writer) !void { .@"struct" => |s| try writer.writeAll(tt.getString(s.name)), .@"enum" => |e| try writer.writeAll(tt.getString(e.name)), .@"union" => |u| try writer.writeAll(tt.getString(u.name)), + .tagged_union => |u| try writer.writeAll(tt.getString(u.name)), .protocol => |p| try writer.writeAll(tt.getString(p.name)), .pointer => |p| { try writer.writeByte('*'); diff --git a/src/ir/type_bridge.zig b/src/ir/type_bridge.zig index 02102ef..94e498c 100644 --- a/src/ir/type_bridge.zig +++ b/src/ir/type_bridge.zig @@ -146,7 +146,7 @@ fn resolveNamedType(name: []const u8, kind: NamedKind, table: *TypeTable) TypeId return switch (kind) { .@"struct" => table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }), .@"enum" => table.intern(.{ .@"enum" = .{ .name = name_id, .variants = &.{} } }), - .@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{}, .tag_type = null } }), + .@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{} } }), }; } @@ -367,10 +367,46 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable) TypeId { .ty = field_ty, }) catch unreachable; } - const info: TypeInfo = .{ .@"union" = .{ + // Resolve backing type and tag type from enum struct + // e.g. enum struct { tag: u32; _: u32; payload: [30]u32; } { ... } + var backing_type: ?TypeId = null; + var tag_type: ?TypeId = null; + if (ed.backing_type) |bt| { + const backing_ty = resolveAstType(bt, table); + backing_type = backing_ty; + // Extract tag type from first field of backing struct + const backing_info = table.get(backing_ty); + if (backing_info == .@"struct") { + if (backing_info.@"struct".fields.len > 0) { + tag_type = backing_info.@"struct".fields[0].ty; + } + } + } + + // Build explicit tag values from variant_values (e.g., quit :: 0x100) + var explicit_tag_vals: ?[]const i64 = null; + if (ed.variant_values.len > 0) { + var vals = std.ArrayList(i64).empty; + for (0..ed.variant_names.len) |i| { + if (i < ed.variant_values.len) { + if (ed.variant_values[i]) |vv| { + if (vv.data == .int_literal) { + vals.append(alloc, vv.data.int_literal.value) catch unreachable; + continue; + } + } + } + vals.append(alloc, @intCast(i)) catch unreachable; + } + explicit_tag_vals = vals.items; + } + + const info: TypeInfo = .{ .tagged_union = .{ .name = name_id, .fields = fields.items, - .tag_type = null, + .tag_type = tag_type orelse .s64, // enum unions are always tagged (default i64) + .backing_type = backing_type, + .explicit_tag_values = explicit_tag_vals, } }; const id = table.intern(info); table.update(id, info); @@ -414,11 +450,21 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable) TypeId { } explicit_vals = vals.items; } + // Resolve backing type for sized enums (e.g. enum u32 { ... }) + var enum_backing: ?TypeId = null; + if (ed.backing_type) |bt| { + // Only use simple backing types (u8, u16, u32, etc.), not struct backing (enum struct) + if (bt.data != .struct_decl) { + enum_backing = resolveAstType(bt, table); + } + } + const info: TypeInfo = .{ .@"enum" = .{ .name = name_id, .variants = variants.items, .is_flags = ed.is_flags, .explicit_values = explicit_vals, + .backing_type = enum_backing, } }; const id = table.intern(info); table.update(id, info); @@ -465,7 +511,6 @@ fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable) TypeId { const info: TypeInfo = .{ .@"union" = .{ .name = name_id, .fields = fields.items, - .tag_type = null, } }; const id = table.intern(info); table.update(id, info); diff --git a/src/ir/types.zig b/src/ir/types.zig index e0f8e0c..9cf4f62 100644 --- a/src/ir/types.zig +++ b/src/ir/types.zig @@ -56,6 +56,7 @@ pub const TypeInfo = union(enum) { @"struct": StructInfo, @"enum": EnumInfo, @"union": UnionInfo, + tagged_union: TaggedUnionInfo, array: ArrayInfo, slice: SliceInfo, pointer: PointerInfo, @@ -84,12 +85,20 @@ pub const TypeInfo = union(enum) { variants: []const StringId, is_flags: bool = false, explicit_values: ?[]const i64 = null, // for flags (power-of-2) or custom values + backing_type: ?TypeId = null, // e.g. u32 for `enum u32 { ... }` }; pub const UnionInfo = struct { name: StringId, fields: []const StructInfo.Field, - tag_type: ?TypeId, // tagged union enum type, null if untagged + }; + + pub const TaggedUnionInfo = struct { + name: StringId, + fields: []const StructInfo.Field, + tag_type: TypeId, // tag integer type (e.g. .u32, .s64) + backing_type: ?TypeId = null, // enum struct backing (e.g. { tag: u32; _: u32; payload: [30]u32; }) + explicit_tag_values: ?[]const i64 = null, // explicit variant values (e.g., quit :: 0x100) }; pub const ArrayInfo = struct { @@ -290,6 +299,7 @@ pub const TypeTable = struct { const n: ?StringId = switch (info) { .@"struct" => |s| s.name, .@"union" => |u| u.name, + .tagged_union => |u| u.name, .@"enum" => |e| e.name, else => null, }; @@ -358,15 +368,27 @@ pub const TypeTable = struct { return if (total == 0) 8 else total; }, .@"union" => |u| { - // Size of union = tag + max(field sizes) var max_field: u32 = 0; for (u.fields) |f| { const sz = self.sizeOf(f.ty); if (sz > max_field) max_field = sz; } - return 8 + @max(max_field, 8); + return @max(max_field, 8); + }, + .tagged_union => |u| { + if (u.backing_type) |bt| return self.sizeOf(bt); + var max_field: u32 = 0; + for (u.fields) |f| { + const sz = self.sizeOf(f.ty); + if (sz > max_field) max_field = sz; + } + const tag_sz = @as(u32, @intCast(self.typeSizeBytes(u.tag_type))); + return tag_sz + @max(max_field, 8); + }, + .@"enum" => |e| { + if (e.backing_type) |bt| return self.sizeOf(bt); + return 8; }, - .@"enum" => 8, // plain enums are just integer tags .tuple => |t| { var total: u32 = 0; for (t.fields) |f| total += @max(self.sizeOf(f), 8); @@ -376,6 +398,145 @@ pub const TypeTable = struct { }; } + /// Compute the ABI size in bytes for a type, matching LLVM's struct layout rules. + /// This is the authoritative size computation used for closure env sizing and + /// verified against LLVMABISizeOfType. + pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize { + if (ty == .void) return 0; + if (ty == .bool) return 1; + if (ty == .u8 or ty == .s8) return 1; + if (ty == .u16 or ty == .s16) return 2; + if (ty == .s32 or ty == .u32 or ty == .f32) return 4; + if (ty == .s64 or ty == .u64 or ty == .f64) return 8; + if (ty == .string) return 16; // {ptr, i64} + if (ty.isBuiltin()) return 8; // default for unknown builtins + const info = self.get(ty); + return switch (info) { + .pointer, .many_pointer, .function => 8, + .slice => 16, // {ptr, i64} + .closure => 16, // {fn_ptr, env_ptr} + .optional => |o| blk: { + const child_info = self.get(o.child); + if (child_info == .pointer or child_info == .many_pointer or child_info == .function) + break :blk 8; + if (child_info == .closure) + break :blk 16; // {fn_ptr, env_ptr} + const cs = self.typeSizeBytes(o.child); + const ca = self.typeAlignBytes(o.child); + // { T, i1 } — i1 goes right after T, then pad to struct alignment + const unpadded = cs + 1; + break :blk (unpadded + ca - 1) & ~(ca - 1); + }, + .@"struct" => |s| blk: { + var offset: usize = 0; + var max_a: usize = 1; + for (s.fields) |f| { + const fs = self.typeSizeBytes(f.ty); + const fa = self.typeAlignBytes(f.ty); + if (fa > max_a) max_a = fa; + offset = (offset + fa - 1) & ~(fa - 1); + offset += fs; + } + break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1); + }, + .@"union" => |u| blk: { + var max_payload: usize = 0; + for (u.fields) |f| { + const fs = self.typeSizeBytes(f.ty); + if (fs > max_payload) max_payload = fs; + } + break :blk if (max_payload == 0) 8 else max_payload; + }, + .tagged_union => |u| blk: { + if (u.backing_type) |bt| break :blk self.typeSizeBytes(bt); + var max_payload: usize = 0; + for (u.fields) |f| { + const fs = self.typeSizeBytes(f.ty); + if (fs > max_payload) max_payload = fs; + } + const tag_size = self.typeSizeBytes(u.tag_type); + const raw = max_payload + tag_size; + break :blk (raw + 7) & ~@as(usize, 7); + }, + .array => |a| blk: { + const elem_size = self.typeSizeBytes(a.element); + break :blk elem_size * @as(usize, @intCast(a.length)); + }, + .vector => |v| blk: { + const elem_size = self.typeSizeBytes(v.element); + const raw = elem_size * @as(usize, @intCast(v.length)); + // LLVM vectors round ABI size up to next power of 2 + break :blk std.math.ceilPowerOfTwo(usize, raw) catch raw; + }, + .tuple => |t| blk: { + var offset: usize = 0; + var max_a: usize = 1; + for (t.fields) |f| { + const fs = self.typeSizeBytes(f); + const fa = self.typeAlignBytes(f); + if (fa > max_a) max_a = fa; + offset = (offset + fa - 1) & ~(fa - 1); + offset += fs; + } + break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1); + }, + .any => 16, + .protocol => 16, + .@"enum" => |e| { + if (e.backing_type) |bt| return self.typeSizeBytes(bt); + return 8; + }, + else => 8, + }; + } + + /// Compute the ABI alignment in bytes for a type, matching LLVM's rules. + pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize { + if (ty == .void) return 1; + if (ty == .bool) return 1; + if (ty == .u8 or ty == .s8) return 1; + if (ty == .u16 or ty == .s16) return 2; + if (ty == .s32 or ty == .u32 or ty == .f32) return 4; + if (ty == .s64 or ty == .u64 or ty == .f64) return 8; + if (ty == .string) return 8; + if (ty.isBuiltin()) return 8; + const info = self.get(ty); + return switch (info) { + .pointer, .many_pointer, .function => 8, + .slice, .closure => 8, + .optional => |o| blk: { + const child_info = self.get(o.child); + if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure) + break :blk 8; + break :blk self.typeAlignBytes(o.child); + }, + .@"struct" => |s| blk: { + var max_a: usize = 1; + for (s.fields) |f| { + const fa = self.typeAlignBytes(f.ty); + if (fa > max_a) max_a = fa; + } + break :blk max_a; + }, + .@"union", .tagged_union => 8, + .@"enum" => |e| { + if (e.backing_type) |bt| return self.typeAlignBytes(bt); + return 8; + }, + .array => |a| self.typeAlignBytes(a.element), + .vector => |v| self.typeAlignBytes(v.element), + .tuple => |t| blk: { + var max_a: usize = 1; + for (t.fields) |f| { + const fa = self.typeAlignBytes(f); + if (fa > max_a) max_a = fa; + } + break :blk max_a; + }, + else => 8, + }; + } + /// Intern a string into the pool. pub fn internString(self: *TypeTable, str: []const u8) StringId { return self.strings.intern(self.alloc, str); @@ -412,6 +573,7 @@ pub const TypeTable = struct { .@"struct" => |s| self.getString(s.name), .@"enum" => |e| self.getString(e.name), .@"union" => |u| self.getString(u.name), + .tagged_union => |u| self.getString(u.name), .protocol => |p| self.getString(p.name), else => "?", }; @@ -471,6 +633,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void { .@"struct" => |s| h.update(std.mem.asBytes(&s.name)), .@"enum" => |e| h.update(std.mem.asBytes(&e.name)), .@"union" => |u| h.update(std.mem.asBytes(&u.name)), + .tagged_union => |u| h.update(std.mem.asBytes(&u.name)), .protocol => |p| h.update(std.mem.asBytes(&p.name)), .tuple => |t| { for (t.fields) |f| h.update(std.mem.asBytes(&f)); @@ -513,6 +676,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool { .@"struct" => |s| s.name == b.@"struct".name, .@"enum" => |e| e.name == b.@"enum".name, .@"union" => |u| u.name == b.@"union".name, + .tagged_union => |u| u.name == b.tagged_union.name, .protocol => |p| p.name == b.protocol.name, .tuple => |t| { const u = b.tuple; diff --git a/src/main.zig b/src/main.zig index e750990..4cc1006 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,9 +1,6 @@ const std = @import("std"); const sx = @import("sx"); -/// Feature flag: use the IR pipeline (parse → lower → IR → LLVM) instead of AST-based codegen. -const USE_IR_PIPELINE = true; - pub fn main(init: std.process.Init) !void { const allocator = init.arena.allocator(); const io = init.io; @@ -24,7 +21,7 @@ pub fn main(init: std.process.Init) !void { // Parse flags and positional arguments var input_path: ?[]const u8 = null; - var target_config = sx.codegen.TargetConfig{}; + var target_config = sx.target.TargetConfig{}; var lib_paths = std.ArrayList([]const u8).empty; var show_timing: bool = false; var explicit_opt: bool = false; @@ -140,25 +137,12 @@ pub fn main(init: std.process.Init) !void { } // Cache MISS — codegen + emit .o to memory (verify skipped: JIT catches errors) - if (USE_IR_PIPELINE) { - comp.generateCodeViaIR() catch { comp.renderErrors(); return; }; - } else { - comp.generateCode() catch { comp.renderErrors(); return; }; - } + comp.generateCode() catch { comp.renderErrors(); return; }; timer.record("codegen"); timer.mark(); - const buf = if (USE_IR_PIPELINE) blk2: { - comp.ir_emitter.?.verifyWithMessage() catch return; - break :blk2 comp.ir_emitter.?.emitObjectToMemory() catch return; - } else - (emit_blk: { - var cg = &comp.cg.?; - break :emit_blk cg.emitObjectToMemory() catch { - comp.renderErrors(); - return; - }; - }); + comp.ir_emitter.?.verifyWithMessage() catch return; + const buf = comp.ir_emitter.?.emitObjectToMemory() catch return; timer.record("emit"); // Save .o to cache (extract data before JIT takes ownership) @@ -175,10 +159,25 @@ pub fn main(init: std.process.Init) !void { defer c_handle.unload(io); timer.record("c-import"); + // dlopen #library dependencies so JIT can resolve foreign symbols + const libs = extractLibraries(allocator, root) catch return; + var lib_handles = std.ArrayList(*anyopaque).empty; + defer { + for (lib_handles.items) |h| _ = std.c.dlclose(h); + } + for (libs) |lib_name| { + if (loadLibrary(allocator, lib_name, target_config.lib_paths)) |handle| { + lib_handles.append(allocator, handle) catch {}; + } else { + const e = std.c.dlerror(); + if (e) |msg| std.debug.print("warning: could not load library '{s}': {s}\n", .{ lib_name, std.mem.span(msg) }); + } + } + // JIT from precompiled object (relocation only, no IR compilation) sx.llvm_api.initNativeTarget(); timer.mark(); - const exit_code = sx.codegen.CodeGen.runJITFromObject(obj_buf) catch { + const exit_code = sx.target.runJITFromObject(obj_buf) catch { // JIT failed — fall back to AOT timer.record("jit-fail"); runAOT(allocator, io, path, target_config, &timer, enable_cache) catch return; @@ -212,7 +211,7 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs); } -fn parseOptLevel(s: []const u8) ?sx.codegen.TargetConfig.OptLevel { +fn parseOptLevel(s: []const u8) ?sx.target.TargetConfig.OptLevel { if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none; if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less; if (std.mem.eql(u8, s, "default") or std.mem.eql(u8, s, "2")) return .default; @@ -294,7 +293,7 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) return try allocator.dupeZ(u8, source_bytes); } -fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing) !sx.core.Compilation { +fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing) !sx.core.Compilation { timer.mark(); const source = try readSource(allocator, io, input_path); timer.record("read"); @@ -311,20 +310,11 @@ fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const timer.record("imports"); timer.mark(); - if (USE_IR_PIPELINE) { - comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; }; - } else { - comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; - } + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; timer.record("codegen"); timer.mark(); - if (USE_IR_PIPELINE) { - comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError; - } else { - var cg = &comp.cg.?; - cg.verify() catch { comp.renderErrors(); return error.CompileError; }; - } + comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError; timer.record("verify"); return comp; @@ -348,18 +338,14 @@ fn dumpSxIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !v std.debug.print("{s}", .{result.items}); } -fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void { +fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void { var timer = Timing.init(false); var comp = try compilePipeline(allocator, io, input_path, target_config, &timer); defer comp.deinit(); - if (USE_IR_PIPELINE) { - comp.ir_emitter.?.printIR(); - } else { - comp.cg.?.printIR(); - } + comp.ir_emitter.?.printIR(); } -fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void { +fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void { var timer = Timing.init(false); var comp = try compilePipeline(allocator, io, input_path, target_config, &timer); defer comp.deinit(); @@ -368,21 +354,17 @@ fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, tar break :blk try std.fmt.allocPrint(allocator, "{s}.s", .{name}); }; const asm_path_z = try allocator.dupeZ(u8, asm_path); - if (USE_IR_PIPELINE) { - comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError; - } else { - comp.cg.?.emitAssembly(asm_path_z.ptr) catch { comp.renderErrors(); return error.CompileError; }; - } + comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError; std.debug.print("emitted: {s}\n", .{asm_path}); } -fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, show_timing: bool, enable_cache: bool) !void { +fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, show_timing: bool, enable_cache: bool) !void { var timer = Timing.init(show_timing); try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, enable_cache); timer.printAll(); } -fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void { +fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void { // Phase A: read + parse + resolveImports (fast: ~0.5ms) timer.mark(); const source = try readSource(allocator, io, input_path); @@ -430,29 +412,15 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons } else { // Cache MISS — full codegen + emit timer.mark(); - if (USE_IR_PIPELINE) { - comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; }; - } else { - comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; - } + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; timer.record("codegen"); timer.mark(); - if (USE_IR_PIPELINE) { - comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError; - } else { - var cg = &comp.cg.?; - cg.verify() catch { comp.renderErrors(); return error.CompileError; }; - } + comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError; timer.record("verify"); timer.mark(); - if (USE_IR_PIPELINE) { - comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError; - } else { - var cg = &comp.cg.?; - cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; }; - } + comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError; timer.record("emit"); // Save .o to cache @@ -471,7 +439,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons // Link (sx .o + C .o files) timer.mark(); - sx.codegen.CodeGen.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch { + sx.target.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch { std.debug.print("error: linking failed\n", .{}); return error.CompileError; }; @@ -489,7 +457,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons } } -fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void { +fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void { const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp"; try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, enable_cache); defer { @@ -518,7 +486,7 @@ fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, targ // --- Cache helpers --- -fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.codegen.TargetConfig) u64 { +fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.target.TargetConfig) u64 { const Wyhash = std.hash.Wyhash; var key = Wyhash.hash(0, source); @@ -583,6 +551,40 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c return try libs.toOwnedSlice(allocator); } +/// Try to dlopen a library by name, searching user paths, host paths, and common naming conventions. +fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_paths: []const []const u8) ?*anyopaque { + const is_macos = comptime @import("builtin").os.tag == .macos; + const suffixes: []const []const u8 = if (is_macos) &.{ ".dylib", ".so" } else &.{ ".so", ".dylib" }; + + // Search paths: user-supplied first, then host defaults + const search_paths = comptime blk: { + var paths: []const []const u8 = &.{}; + for (sx.target.host_lib_paths) |p| { + paths = paths ++ .{p}; + } + break :blk paths; + }; + + // Try each path with each suffix + const all_paths = [_][]const []const u8{ user_lib_paths, search_paths }; + for (&all_paths) |paths| { + for (paths) |dir| { + for (suffixes) |sfx| { + const full = std.fmt.allocPrintSentinel(allocator, "{s}/lib{s}{s}", .{ dir, lib_name, sfx }, 0) catch continue; + if (std.c.dlopen(full.ptr, .{ .NOW = true })) |h| return h; + } + } + } + + // Fallback: bare name (let dlopen search its default paths) + for (suffixes) |sfx| { + const bare = std.fmt.allocPrintSentinel(allocator, "lib{s}{s}", .{ lib_name, sfx }, 0) catch continue; + if (std.c.dlopen(bare.ptr, .{ .NOW = true })) |h| return h; + } + + return null; +} + // Simple timing helper — records stage durations and prints a summary table. const Timing = struct { const max_entries = 16; diff --git a/src/root.zig b/src/root.zig index 5df44cf..9db1619 100644 --- a/src/root.zig +++ b/src/root.zig @@ -4,7 +4,7 @@ pub const lexer = @import("lexer.zig"); pub const ast = @import("ast.zig"); pub const parser = @import("parser.zig"); pub const types = @import("types.zig"); -pub const codegen = @import("codegen.zig"); +pub const target = @import("target.zig"); pub const builtins = @import("builtins.zig"); pub const errors = @import("errors.zig"); pub const sema = @import("sema.zig"); diff --git a/src/target.zig b/src/target.zig new file mode 100644 index 0000000..5aeac66 --- /dev/null +++ b/src/target.zig @@ -0,0 +1,206 @@ +const std = @import("std"); +const llvm = @import("llvm_api.zig"); +const c = llvm.c; + +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"; + } +}; + +/// 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, 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, extra_objects: []const []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 }); + for (extra_objects) |eo| try argv.append(allocator, eo); + 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 }); + for (extra_objects) |eo| try argv.append(allocator, eo); + + 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. +pub 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; +}; diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index 796e653..49caee0 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -410,6 +410,9 @@ opt field set: 42 opt param a: 42 opt param b: 0 opt param 7: 7 +opt reassign: 42.500000 +opt compute assign: 15.000000 +opt re-null: 99.000000 generic opt 1: 5 generic opt 2: 7 generic opt 3: null