11604 lines
561 KiB
Zig
11604 lines
561 KiB
Zig
const std = @import("std");
|
|
const ast = @import("ast.zig");
|
|
const Node = ast.Node;
|
|
const Span = ast.Span;
|
|
const llvm = @import("llvm_api.zig");
|
|
const c = llvm.c;
|
|
const types = @import("types.zig");
|
|
const Type = types.Type;
|
|
const Builtins = @import("builtins.zig").Builtins;
|
|
const Parser = @import("parser.zig").Parser;
|
|
const errors = @import("errors.zig");
|
|
const sema = @import("sema.zig");
|
|
const comptime_mod = @import("comptime.zig");
|
|
const unescape = @import("unescape.zig");
|
|
|
|
pub const TargetConfig = struct {
|
|
/// Target triple (e.g. "aarch64-apple-darwin"). Null = host default.
|
|
triple: ?[*:0]const u8 = null,
|
|
/// CPU name (e.g. "generic", "apple-m1"). Null = "generic".
|
|
cpu: ?[*:0]const u8 = null,
|
|
/// CPU features string (e.g. "+avx2"). Null = "".
|
|
features: ?[*:0]const u8 = null,
|
|
/// Optimization level.
|
|
opt_level: OptLevel = .default,
|
|
/// Library search paths (-L flags).
|
|
lib_paths: []const []const u8 = &.{},
|
|
/// Output path override.
|
|
output_path: ?[]const u8 = null,
|
|
/// Linker command (null = "cc" on Unix, "link.exe" on Windows).
|
|
linker: ?[]const u8 = null,
|
|
/// Sysroot for cross-compilation (passed as --sysroot to linker).
|
|
sysroot: ?[]const u8 = null,
|
|
|
|
pub const OptLevel = enum {
|
|
none,
|
|
less,
|
|
default,
|
|
aggressive,
|
|
|
|
pub fn toLLVM(self: OptLevel) c.LLVMCodeGenOptLevel {
|
|
return switch (self) {
|
|
.none => c.LLVMCodeGenLevelNone,
|
|
.less => c.LLVMCodeGenLevelLess,
|
|
.default => c.LLVMCodeGenLevelDefault,
|
|
.aggressive => c.LLVMCodeGenLevelAggressive,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Check if target triple indicates aarch64/arm64 (runtime check, not comptime).
|
|
pub fn isAarch64(self: TargetConfig) bool {
|
|
return self.tripleHasPrefix("aarch64", "arm64");
|
|
}
|
|
|
|
/// Check if target triple indicates x86_64/x86-64.
|
|
pub fn isX86_64(self: TargetConfig) bool {
|
|
return self.tripleHasPrefix("x86_64", "x86-64");
|
|
}
|
|
|
|
/// Check if target triple indicates Windows (contains "windows" or "win32").
|
|
pub fn isWindows(self: TargetConfig) bool {
|
|
return self.tripleContains("windows") or self.tripleContains("win32");
|
|
}
|
|
|
|
fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool {
|
|
if (self.triple) |t| {
|
|
const span = std.mem.span(t);
|
|
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
|
|
}
|
|
const dt = c.LLVMGetDefaultTargetTriple();
|
|
defer c.LLVMDisposeMessage(dt);
|
|
const span = std.mem.span(dt);
|
|
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
|
|
}
|
|
|
|
fn tripleContains(self: TargetConfig, needle: []const u8) bool {
|
|
if (self.triple) |t| {
|
|
return std.mem.indexOf(u8, std.mem.span(t), needle) != null;
|
|
}
|
|
const dt = c.LLVMGetDefaultTargetTriple();
|
|
defer c.LLVMDisposeMessage(dt);
|
|
return std.mem.indexOf(u8, std.mem.span(dt), needle) != null;
|
|
}
|
|
|
|
pub fn getCpu(self: TargetConfig) [*:0]const u8 {
|
|
return self.cpu orelse "generic";
|
|
}
|
|
|
|
pub fn getFeatures(self: TargetConfig) [*:0]const u8 {
|
|
return self.features orelse "";
|
|
}
|
|
|
|
pub fn getLinker(self: TargetConfig) []const u8 {
|
|
return self.linker orelse "cc";
|
|
}
|
|
};
|
|
|
|
fn baseName(name: []const u8) []const u8 {
|
|
return if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name;
|
|
}
|
|
|
|
/// 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 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),
|
|
.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.
|
|
/// No LLVM state save/restore needed — the VM operates independently.
|
|
fn comptimeEval(self: *CodeGen, expr: *Node, expected_type: Type) !comptime_mod.Value {
|
|
_ = expected_type; // VM infers types from values; expected_type used by caller for LLVM conversion
|
|
|
|
var compiler = comptime_mod.Compiler.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self);
|
|
const chunk = compiler.compile(expr) catch |err| {
|
|
return self.emitErrorFmt("comptime compilation failed: {s}", .{@errorName(err)});
|
|
};
|
|
|
|
var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self);
|
|
vm.setupComptimeContext() catch {};
|
|
return vm.execute(&chunk) catch |err| {
|
|
return self.emitErrorFmt("comptime execution failed: {s}", .{@errorName(err)});
|
|
};
|
|
}
|
|
|
|
/// 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);
|
|
},
|
|
.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);
|
|
},
|
|
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 ta = cd.type_annotation orelse return; // need explicit type for top-level constants
|
|
const sx_ty = self.resolveType(ta);
|
|
if (sx_ty == .void_type) return;
|
|
|
|
const const_val = self.evalConstant(cd.value, sx_ty) orelse return;
|
|
|
|
const name_z = try self.allocator.dupeZ(u8, cd.name);
|
|
const global = c.LLVMAddGlobal(self.module, self.typeToLLVM(sx_ty), name_z.ptr);
|
|
c.LLVMSetInitializer(global, const_val);
|
|
c.LLVMSetGlobalConstant(global, 1);
|
|
|
|
try self.comptime_globals.put(cd.name, .{
|
|
.global = global,
|
|
.ty = sx_ty,
|
|
.expr = cd.value,
|
|
.is_resolved = true,
|
|
});
|
|
}
|
|
|
|
fn registerGlobalVar(self: *CodeGen, vd: ast.VarDecl) !void {
|
|
const ta = vd.type_annotation orelse return;
|
|
const sx_ty = self.resolveType(ta);
|
|
if (sx_ty == .void_type) return;
|
|
|
|
const llvm_ty = self.typeToLLVM(sx_ty);
|
|
const name_z = try self.allocator.dupeZ(u8, vd.name);
|
|
const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr);
|
|
|
|
// 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);
|
|
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) {
|
|
// 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 el = vd.value.?.data.enum_literal;
|
|
const lit_alloca = try self.genTaggedEnumLiteral(el, uname);
|
|
try self.registerVariable(vd.name, lit_alloca, sx_ty);
|
|
return null;
|
|
} else if (vd.value.?.data == .call) {
|
|
// Call returning a union — could be enum construction (alloca) or function call (value)
|
|
const result = try self.genExpr(vd.value.?);
|
|
_ = c.LLVMBuildStore(self.builder, self.loadIfPointer(result, info.llvm_type, "union_load"), alloca);
|
|
} else {
|
|
// Other expression — try genExprAsType
|
|
const result = try self.genExprAsType(vd.value.?, sx_ty);
|
|
_ = c.LLVMBuildStore(self.builder, self.loadIfPointer(result, info.llvm_type, "union_load"), alloca);
|
|
}
|
|
|
|
try self.registerVariable(vd.name, alloca, sx_ty);
|
|
return null;
|
|
}
|
|
|
|
// Array-typed variable
|
|
if (sx_ty.isArray()) {
|
|
const arr_info = sx_ty.array_type;
|
|
const llvm_arr_ty = self.typeToLLVM(sx_ty);
|
|
const arr_alloca = try self.buildNamedAlloca(llvm_arr_ty, vd.name);
|
|
|
|
if (vd.value == null) {
|
|
self.storeNull(llvm_arr_ty, arr_alloca);
|
|
} else if (vd.value.?.data == .undef_literal) {
|
|
self.storeUndef(llvm_arr_ty, arr_alloca);
|
|
} else if (vd.value.?.data == .array_literal) {
|
|
const al = vd.value.?.data.array_literal;
|
|
const elem_sx_ty = try self.resolveElementType(arr_info.element_name, "array");
|
|
const elem_llvm_ty = self.typeToLLVM(elem_sx_ty);
|
|
const len = @min(al.elements.len, arr_info.length);
|
|
for (0..len) |i| {
|
|
const 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, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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: <int_type>; payload: [N]<type>; }}",
|
|
.{ 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: <int_type>; payload: [N]<type>; }}",
|
|
.{ 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, el: ast.EnumLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef {
|
|
const uname = expected_union_name orelse
|
|
(if (self.current_return_type.isUnion()) self.current_return_type.union_type else null) orelse
|
|
return self.emitError("cannot infer enum type for literal");
|
|
const resolved_name = self.resolveAlias(uname);
|
|
const info = try self.getTaggedEnumInfo(resolved_name);
|
|
|
|
// Find variant index
|
|
var variant_idx: ?u32 = null;
|
|
for (info.variant_names, 0..) |vn, i| {
|
|
if (std.mem.eql(u8, vn, el.name)) {
|
|
variant_idx = @intCast(i);
|
|
break;
|
|
}
|
|
}
|
|
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ el.name, resolved_name });
|
|
|
|
// Alloca union
|
|
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp");
|
|
const tag_ty = self.getEnumLLVMType(resolved_name);
|
|
|
|
// Store tag (field 0) — use explicit value if available, otherwise index
|
|
const tag_gep = self.structGEP(info.llvm_type, alloca, 0, "tag");
|
|
const tag_val: u64 = if (self.enum_variant_values.get(resolved_name)) |vals| @bitCast(vals[idx]) else idx;
|
|
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(tag_ty, tag_val, 0), tag_gep);
|
|
|
|
// Store payload (field 1) if not void
|
|
if (el.payload) |payload_node| {
|
|
const variant_ty = info.variant_types[idx];
|
|
if (variant_ty != .void_type) {
|
|
const payload_val = try self.genExprAsType(payload_node, variant_ty);
|
|
self.storeStructField(info.llvm_type, alloca, info.payload_field_index, payload_val);
|
|
}
|
|
}
|
|
|
|
return alloca;
|
|
}
|
|
|
|
fn genStructLiteral(self: *CodeGen, sl: ast.StructLiteral, expected_struct_name: ?[]const u8) anyerror!c.LLVMValueRef {
|
|
const raw_name = sl.struct_name orelse blk: {
|
|
if (sl.type_expr) |te| {
|
|
const ty = self.resolveType(te);
|
|
if (ty.isStruct()) break :blk ty.struct_type;
|
|
}
|
|
break :blk expected_struct_name orelse return self.emitError("cannot infer struct type for literal");
|
|
};
|
|
// Resolve type aliases (e.g. Vec3 -> Vec__3_f32)
|
|
const sname = self.resolveAlias(raw_name);
|
|
const info = try self.getStructInfo(sname);
|
|
|
|
// Alloca the struct and default-init all fields (zero or declared defaults)
|
|
const name_z = try self.allocator.dupeZ(u8, sname);
|
|
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
|
|
try self.genStructDefaultInit(alloca, info);
|
|
|
|
// Determine if this is named or positional mode
|
|
var has_named = false;
|
|
for (sl.field_inits) |fi| {
|
|
if (fi.name != null) {
|
|
has_named = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (has_named) {
|
|
// Named/shorthand mode: map by field name
|
|
for (sl.field_inits) |fi| {
|
|
const fname = fi.name orelse {
|
|
// Positional field mixed with named — treat as identifier shorthand
|
|
if (fi.value.data == .identifier) {
|
|
const idx = try self.findFieldIndex(info.field_names, fi.value.data.identifier.name, sname);
|
|
const val = try self.genExprAsType(fi.value, info.field_types[idx]);
|
|
self.storeStructField(info.llvm_type, alloca, @intCast(idx), val);
|
|
continue;
|
|
}
|
|
return self.emitError("mixed positional and named fields in struct literal");
|
|
};
|
|
const idx = try self.findFieldIndex(info.field_names, fname, sname);
|
|
const val = try self.genExprAsType(fi.value, info.field_types[idx]);
|
|
self.storeStructField(info.llvm_type, alloca, @intCast(idx), val);
|
|
}
|
|
} else {
|
|
// Positional mode: assign in order
|
|
for (sl.field_inits, 0..) |fi, i| {
|
|
if (i >= info.field_names.len) return self.emitErrorFmt("too many fields in struct literal (expected {d})", .{info.field_names.len});
|
|
const val = try self.genExprAsType(fi.value, info.field_types[i]);
|
|
self.storeStructField(info.llvm_type, alloca, @intCast(i), val);
|
|
}
|
|
}
|
|
|
|
return alloca;
|
|
}
|
|
|
|
/// 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;
|
|
const val = try self.genExpr(inner);
|
|
const src_ty = self.inferType(inner);
|
|
return self.convertValue(val, src_ty, target_ty);
|
|
}
|
|
|
|
// Function pointer target: bypass narrowing check, just produce the pointer value
|
|
if (target_ty.isFunctionType()) {
|
|
return try self.genExpr(node);
|
|
}
|
|
|
|
// String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping)
|
|
if (node.data == .string_literal and target_ty.isPointer()) {
|
|
const lit = node.data.string_literal;
|
|
const content = if (lit.is_raw) lit.raw else try unescape.unescapeString(self.allocator, lit.raw);
|
|
const str_z = try self.allocator.dupeZ(u8, content);
|
|
return self.buildGlobalString(str_z.ptr, "str");
|
|
}
|
|
|
|
// 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 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);
|
|
}
|
|
|
|
// 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()) {
|
|
const el = node.data.enum_literal;
|
|
return self.genTaggedEnumLiteral(el, target_ty.union_type);
|
|
}
|
|
|
|
// Struct literal targeting union type: .Variant.{fields} pattern
|
|
// Parsed as struct_literal with type_expr = enum_literal("Variant")
|
|
if (node.data == .struct_literal and target_ty.isUnion()) {
|
|
const sl = node.data.struct_literal;
|
|
if (sl.struct_name == null) {
|
|
if (sl.type_expr) |te| {
|
|
if (te.data == .enum_literal) {
|
|
const variant_name = te.data.enum_literal.name;
|
|
const uname = self.resolveAlias(target_ty.union_type);
|
|
const info = try self.getTaggedEnumInfo(uname);
|
|
|
|
// Find variant index
|
|
var variant_idx: ?u32 = null;
|
|
for (info.variant_names, 0..) |vn, vi| {
|
|
if (std.mem.eql(u8, vn, variant_name)) {
|
|
variant_idx = @intCast(vi);
|
|
break;
|
|
}
|
|
}
|
|
const idx = variant_idx orelse
|
|
return self.emitErrorFmt("no variant '{s}' in enum '{s}'", .{ variant_name, uname });
|
|
|
|
const variant_ty = info.variant_types[idx];
|
|
|
|
// Alloca union, store tag
|
|
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_lit");
|
|
const tag_llvm_ty = self.getEnumLLVMType(uname);
|
|
self.storeStructField(info.llvm_type, alloca, 0, c.LLVMConstInt(tag_llvm_ty, idx, 0));
|
|
|
|
// Store struct payload
|
|
if (variant_ty != .void_type) {
|
|
const payload_struct_name = if (variant_ty.isStruct()) variant_ty.struct_type else null;
|
|
const payload_alloca = try self.genStructLiteral(.{
|
|
.struct_name = payload_struct_name,
|
|
.type_expr = null,
|
|
.field_inits = sl.field_inits,
|
|
}, payload_struct_name);
|
|
const payload_gep = self.structGEP(info.llvm_type, alloca, info.payload_field_index, "payload");
|
|
const payload_llvm_ty = self.typeToLLVM(variant_ty);
|
|
const struct_val = c.LLVMBuildLoad2(self.builder, payload_llvm_ty, payload_alloca, "struct_load");
|
|
_ = c.LLVMBuildStore(self.builder, struct_val, payload_gep);
|
|
}
|
|
|
|
return alloca;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Struct literal targeting struct type: pass struct name context
|
|
if (node.data == .struct_literal and target_ty.isStruct()) {
|
|
const alloca = try self.genStructLiteral(node.data.struct_literal, target_ty.struct_type);
|
|
// genStructLiteral returns an alloca pointer — load the value for by-value passing
|
|
const sname = self.resolveAlias(target_ty.struct_type);
|
|
if (self.lookupStructInfo(sname)) |si| {
|
|
return c.LLVMBuildLoad2(self.builder, si.llvm_type, alloca, "struct_val");
|
|
}
|
|
return alloca;
|
|
}
|
|
|
|
// Array literal with target array type: generate with element conversion
|
|
if (node.data == .array_literal and target_ty.isArray()) {
|
|
return self.genArrayLiteral(node.data.array_literal, target_ty);
|
|
}
|
|
|
|
// Array literal with target vector type: build via undef + InsertElement
|
|
if (node.data == .array_literal and target_ty.isVector()) {
|
|
return self.genVectorLiteral(node.data.array_literal, target_ty);
|
|
}
|
|
|
|
// Array literal with target slice type: build stack-backed slice
|
|
if (node.data == .array_literal and target_ty.isSlice()) {
|
|
return self.genSliceLiteral(node.data.array_literal, target_ty);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
// Non-identifier expressions (e.g. field access, tuple element):
|
|
// generate value, store into temp alloca, return alloca pointer
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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});
|
|
}
|
|
}
|
|
}
|
|
// 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 {
|
|
if (call_node.callee.data == .field_access) {
|
|
const fa = call_node.callee.data.field_access;
|
|
|
|
// Union construction: Shape.variant(payload)
|
|
const resolved_type: ?Type = blk: {
|
|
if (fa.object.data == .identifier) {
|
|
const name = self.resolveAlias(fa.object.data.identifier.name);
|
|
if (self.type_registry.get(name)) |e| switch (e) {
|
|
.tagged_enum => break :blk Type{ .union_type = name },
|
|
.struct_info => break :blk Type{ .struct_type = name },
|
|
else => {},
|
|
};
|
|
} else {
|
|
const ty = self.resolveType(fa.object);
|
|
if (ty.isUnion() or ty.isStruct()) break :blk ty;
|
|
}
|
|
break :blk null;
|
|
};
|
|
if (resolved_type) |rty| {
|
|
if (rty.isUnion()) {
|
|
const type_name = rty.union_type;
|
|
const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null;
|
|
return self.genTaggedEnumLiteral(.{
|
|
.name = fa.field,
|
|
.payload = payload_node,
|
|
}, type_name);
|
|
}
|
|
}
|
|
|
|
// Namespaced call: namespace.func(args)
|
|
if (fa.object.data == .identifier) {
|
|
const ns_name = fa.object.data.identifier.name;
|
|
if (self.namespaces.contains(ns_name)) {
|
|
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field });
|
|
return self.genCallByName(qualified, call_node);
|
|
}
|
|
}
|
|
|
|
// 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_<name>(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 use i64
|
|
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 i64_type;
|
|
const merge_bb = self.appendBB("match_end");
|
|
|
|
// Create case basic blocks
|
|
var case_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty;
|
|
for (match.arms) |_| {
|
|
try case_bbs.append(self.allocator, self.appendBB("case"));
|
|
}
|
|
|
|
// Find else arm (null pattern) — use its BB as the switch default
|
|
var else_arm_idx: ?usize = null;
|
|
for (match.arms, 0..) |arm, i| {
|
|
if (arm.pattern == null) {
|
|
else_arm_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
const default_bb = if (else_arm_idx) |idx|
|
|
case_bbs.items[idx]
|
|
else
|
|
self.appendBB("match_default");
|
|
|
|
// Build switch instruction
|
|
const sw = c.LLVMBuildSwitch(self.builder, subject_val, default_bb, @intCast(match.arms.len));
|
|
for (match.arms, 0..) |arm, i| {
|
|
const pat = arm.pattern orelse continue; // skip else arm
|
|
if (pat.data == .enum_literal) {
|
|
const idx = self.lookupVariantValue(enum_name orelse union_name, variants, pat.data.enum_literal.name);
|
|
const case_val = c.LLVMConstInt(case_int_type, idx, 0);
|
|
c.LLVMAddCase(sw, case_val, case_bbs.items[i]);
|
|
} else if (pat.data == .type_expr) {
|
|
// Type-match: resolve type name to Any tag value(s)
|
|
const tag_values = try self.resolveTypeMatchTags(pat.data.type_expr.name);
|
|
for (tag_values) |tag| {
|
|
c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]);
|
|
}
|
|
} else if (pat.data == .identifier) {
|
|
// Named type (struct/enum/union name) or category (int/float)
|
|
const tag_values = try self.resolveTypeMatchTags(pat.data.identifier.name);
|
|
for (tag_values) |tag| {
|
|
c.LLVMAddCase(sw, c.LLVMConstInt(i64_type, tag, 0), case_bbs.items[i]);
|
|
}
|
|
} 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| {
|
|
// 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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 = c.LLVMPrintModuleToString(self.module);
|
|
defer c.LLVMDisposeMessage(ir);
|
|
const len = std.mem.len(ir);
|
|
std.debug.print("{s}\n", .{ir[0..len]});
|
|
}
|
|
|
|
fn emitToFile(self: *CodeGen, output_path: [*:0]const u8, file_type: c.LLVMCodeGenFileType) !void {
|
|
const tm = self.target_machine orelse return self.emitError("no target machine available");
|
|
|
|
var err_msg: [*c]u8 = null;
|
|
if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, file_type, &err_msg) != 0) {
|
|
defer c.LLVMDisposeMessage(err_msg);
|
|
const msg = std.mem.span(err_msg);
|
|
return self.emitErrorFmt("failed to emit file: {s}", .{msg});
|
|
}
|
|
}
|
|
|
|
pub fn emitObject(self: *CodeGen, output_path: [*:0]const u8) !void {
|
|
return self.emitToFile(output_path, c.LLVMObjectFile);
|
|
}
|
|
|
|
pub fn emitAssembly(self: *CodeGen, output_path: [*:0]const u8) !void {
|
|
return self.emitToFile(output_path, c.LLVMAssemblyFile);
|
|
}
|
|
|
|
/// Emit the module as an object file to a memory buffer.
|
|
/// Caller owns the returned buffer and must dispose or pass to JIT.
|
|
pub fn emitObjectToMemory(self: *CodeGen) !c.LLVMMemoryBufferRef {
|
|
const tm = self.target_machine orelse return self.emitError("no target machine available");
|
|
var err_msg: [*c]u8 = null;
|
|
var buf: c.LLVMMemoryBufferRef = null;
|
|
if (c.LLVMTargetMachineEmitToMemoryBuffer(tm, self.module, c.LLVMObjectFile, &err_msg, &buf) != 0) {
|
|
if (err_msg != null) {
|
|
defer c.LLVMDisposeMessage(err_msg);
|
|
const msg = std.mem.span(err_msg);
|
|
return self.emitErrorFmt("failed to emit object to memory: {s}", .{msg});
|
|
}
|
|
return error.CompileError;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/// Execute a precompiled object file in-process using LLVM's ORC JIT.
|
|
/// Takes ownership of obj_buf. Returns the exit code from main().
|
|
pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
|
|
// Create LLJIT with default builder (no custom TM needed — .o is precompiled)
|
|
var jit: c.LLVMOrcLLJITRef = null;
|
|
var err = c.LLVMOrcCreateLLJIT(&jit, null);
|
|
if (err != null) {
|
|
const msg = c.LLVMGetErrorMessage(err);
|
|
defer c.LLVMDisposeErrorMessage(msg);
|
|
std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)});
|
|
return error.CompileError;
|
|
}
|
|
defer _ = c.LLVMOrcDisposeLLJIT(jit);
|
|
|
|
// Add process symbols so JIT can find libc (printf, 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;
|
|
};
|
|
};
|