smoke tests

This commit is contained in:
agra
2026-02-15 19:44:20 +02:00
parent a3be9cce7c
commit 7da3ecfa7c
9 changed files with 1046 additions and 149 deletions

View File

@@ -89,6 +89,7 @@ pub const FnDecl = struct {
return_type: ?*Node,
body: *Node,
type_params: []const StructTypeParam = &.{},
is_arrow: bool = false,
};
pub const Param = struct {

View File

@@ -107,17 +107,7 @@ pub const CodeGen = struct {
// Symbol table: maps variable names to their alloca pointers
named_values: std.StringHashMap(NamedValue),
// Enum type registry: maps enum name to variant list
enum_types: std.StringHashMap([]const []const u8),
// Type alias registry: maps alias name to target type name
type_aliases: std.StringHashMap([]const u8),
// Struct type registry: maps struct name to field info + LLVM type
struct_types: std.StringHashMap(StructInfo),
// Tagged enum registry: maps name to variant info + LLVM type (enums with payloads)
tagged_enum_types: std.StringHashMap(TaggedEnumInfo),
// Union registry: maps name to field info + LLVM type (untagged, C-style)
union_types: std.StringHashMap(UnionInfo),
// Unified type registry: single lookup for all named types
// 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),
@@ -292,6 +282,34 @@ pub const CodeGen = struct {
ty: Type, // sx type
};
/// 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 {
const ctx = c.LLVMContextCreate();
const module = c.LLVMModuleCreateWithNameInContext(module_name, ctx);
@@ -331,11 +349,6 @@ pub const CodeGen = struct {
.builder = builder,
.allocator = allocator,
.named_values = std.StringHashMap(NamedValue).init(allocator),
.enum_types = std.StringHashMap([]const []const u8).init(allocator),
.type_aliases = std.StringHashMap([]const u8).init(allocator),
.struct_types = std.StringHashMap(StructInfo).init(allocator),
.tagged_enum_types = std.StringHashMap(TaggedEnumInfo).init(allocator),
.union_types = std.StringHashMap(UnionInfo).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),
@@ -367,11 +380,6 @@ pub const CodeGen = struct {
pub fn deinit(self: *CodeGen) void {
self.named_values.deinit();
self.enum_types.deinit();
self.type_aliases.deinit();
self.struct_types.deinit();
self.tagged_enum_types.deinit();
self.union_types.deinit();
self.type_registry.deinit();
self.comptime_globals.deinit();
self.enum_backing_types.deinit();
@@ -393,11 +401,52 @@ pub const CodeGen = struct {
}
fn getStructInfo(self: *CodeGen, name: []const u8) !StructInfo {
return self.struct_types.get(name) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{name});
return self.lookupStructInfo(name) orelse
return self.emitErrorFmt("unknown struct type '{s}'", .{name});
}
fn getTaggedEnumInfo(self: *CodeGen, name: []const u8) !TaggedEnumInfo {
return self.tagged_enum_types.get(name) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{name});
return self.lookupTaggedEnumInfo(name) orelse
return self.emitErrorFmt("unknown enum type '{s}'", .{name});
}
fn lookupStructInfo(self: *CodeGen, name: []const u8) ?StructInfo {
if (self.type_registry.get(name)) |e| {
if (e == .struct_info) return e.struct_info;
}
return null;
}
fn lookupTaggedEnumInfo(self: *CodeGen, name: []const u8) ?TaggedEnumInfo {
if (self.type_registry.get(name)) |e| {
if (e == .tagged_enum) return e.tagged_enum;
}
return null;
}
fn lookupUnionInfo(self: *CodeGen, name: []const u8) ?UnionInfo {
if (self.type_registry.get(name)) |e| {
if (e == .union_info) return e.union_info;
}
return null;
}
fn lookupEnumVariants(self: *CodeGen, name: []const u8) ?[]const []const u8 {
if (self.type_registry.get(name)) |e| {
if (e == .plain_enum) return e.plain_enum;
}
return null;
}
fn lookupAlias(self: *CodeGen, name: []const u8) ?[]const u8 {
if (self.type_registry.get(name)) |e| {
if (e == .alias) return e.alias;
}
return null;
}
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 {
@@ -419,6 +468,14 @@ pub const CodeGen = struct {
return self.builtins orelse return self.emitError("builtins not available");
}
/// Unified value lookup: checks locals, comptime globals, then global mutables.
fn lookupValue(self: *CodeGen, name: []const u8) ?ValueLookup {
if (self.named_values.get(name)) |nv| return .{ .local = nv };
if (self.comptime_globals.getPtr(name)) |ct| return .{ .comptime_global = ct };
if (self.global_mutable_vars.get(name)) |gm| return .{ .global_mutable = gm };
return null;
}
/// Build an alloca in the entry block of the current function so that
/// stack space is reserved once, not on every loop iteration.
fn buildEntryBlockAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: [*:0]const u8) c.LLVMValueRef {
@@ -461,8 +518,8 @@ pub const CodeGen = struct {
.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.struct_types.get(name)) |info| info.llvm_type else unreachable,
.union_type => |name| if (self.tagged_enum_types.get(name)) |info| info.llvm_type else if (self.union_types.get(name)) |info| info.llvm_type else unreachable,
.struct_type => |name| if (self.lookupStructInfo(name)) |info| info.llvm_type else unreachable,
.union_type => |name| if (self.lookupTaggedEnumInfo(name)) |info| info.llvm_type else if (self.lookupUnionInfo(name)) |info| info.llvm_type else unreachable,
.array_type => |info| {
const elem_ty = Type.fromName(info.element_name) orelse unreachable;
return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length);
@@ -554,7 +611,7 @@ pub const CodeGen = struct {
.struct_type => |name| {
_ = try self.getAnyTypeId(name, sx_type);
// Recursively register struct field types
if (self.struct_types.get(name)) |info| {
if (self.lookupStructInfo(name)) |info| {
for (info.field_types) |ft| {
try self.preRegisterAnyType(ft);
}
@@ -610,6 +667,7 @@ pub const CodeGen = struct {
// 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")
@@ -628,7 +686,7 @@ pub const CodeGen = struct {
.string_type => self.allocaStoreAsI64(self.getStringStructType(), val, "any_str"),
.struct_type => |sname| blk: {
// Struct — store to alloca, pass pointer as i64
const info = self.struct_types.get(sname) orelse
const info = self.lookupStructInfo(sname) orelse
return self.getUndef(any_ty);
break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_struct");
},
@@ -643,7 +701,7 @@ pub const CodeGen = struct {
},
.union_type => |uname| blk: {
// Union — store to alloca, pass pointer as i64
const info = self.tagged_enum_types.get(uname) orelse
const info = self.lookupTaggedEnumInfo(uname) orelse
return self.getUndef(any_ty);
break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_union");
},
@@ -754,7 +812,7 @@ pub const CodeGen = struct {
}
fn resolveAlias(self: *CodeGen, name: []const u8) []const u8 {
return self.type_aliases.get(name) orelse name;
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 {
@@ -982,7 +1040,6 @@ pub const CodeGen = struct {
try self.registerTaggedEnum(ed);
} else {
// Payload-less enum
try self.enum_types.put(ed.name, ed.variant_names);
try self.type_registry.put(ed.name, .{ .plain_enum = ed.variant_names });
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
@@ -1025,7 +1082,6 @@ pub const CodeGen = struct {
} else if (cd.value.data == .lambda) {
try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda);
} else if (cd.value.data == .type_expr) {
try self.type_aliases.put(cd.name, cd.value.data.type_expr.name);
try self.type_registry.put(cd.name, .{ .alias = cd.value.data.type_expr.name });
} else if (cd.value.data == .call) {
// Check if this is a generic struct or type function instantiation
@@ -1038,24 +1094,20 @@ pub const CodeGen = struct {
// Generic struct instantiation: Vec3 :: Vec(3, f32);
const result_ty = try self.instantiateGenericStruct(cn, tmpl, cd.value.data.call.args);
if (result_ty.isStruct()) {
try self.type_aliases.put(cd.name, result_ty.struct_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type });
}
} else if (self.generic_templates.get(cn)) |tmpl| {
// Type-returning function: Foo :: Complex(u32);
const result_ty = try self.instantiateTypeFunction(cd.name, cn, tmpl, cd.value.data.call.args);
if (result_ty.isStruct()) {
try self.type_aliases.put(cd.name, result_ty.struct_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type });
} else if (result_ty.isUnion()) {
try self.type_aliases.put(cd.name, result_ty.union_type);
try self.type_registry.put(cd.name, .{ .alias = result_ty.union_type });
}
} else if (self.builtin_functions.contains(cn)) {
// Builtin type function (e.g., Vector(4, f32), Array(5, s32))
if (self.resolveBuiltinType(cn, cd.value.data.call.args)) |result_ty| {
const display = try result_ty.displayName(self.allocator);
try self.type_aliases.put(cd.name, display);
try self.type_registry.put(cd.name, .{ .alias = display });
} else {
try self.registerTopLevelConstant(cd);
@@ -1405,7 +1457,7 @@ pub const CodeGen = struct {
const mangled_name = try self.mangleGenericName(template_name, sd.type_params, type_bindings, val_bindings, null);
// Check if already instantiated
if (self.struct_types.contains(mangled_name)) {
if (self.type_registry.contains(mangled_name)) {
return .{ .struct_type = mangled_name };
}
@@ -1468,7 +1520,6 @@ pub const CodeGen = struct {
.type_param_types = try tp_types.toOwnedSlice(self.allocator),
.template_name = template_name,
};
try self.struct_types.put(mangled_name, si);
try self.type_registry.put(mangled_name, .{ .struct_info = si });
_ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name });
@@ -1497,7 +1548,7 @@ pub const CodeGen = struct {
// Try struct first
if (self.findStructInBody(fd.body)) |struct_decl| {
if (self.struct_types.contains(mangled_name)) {
if (self.type_registry.contains(mangled_name)) {
return .{ .struct_type = mangled_name };
}
return self.registerInstantiatedStruct(mangled_name, alias_name, struct_decl);
@@ -1505,7 +1556,7 @@ pub const CodeGen = struct {
// Try union
if (self.findUnionInBody(fd.body)) |union_decl| {
if (self.tagged_enum_types.contains(mangled_name)) {
if (self.type_registry.contains(mangled_name)) {
return .{ .union_type = mangled_name };
}
return self.registerInstantiatedTaggedEnum(mangled_name, union_decl);
@@ -1527,7 +1578,6 @@ pub const CodeGen = struct {
.llvm_type = build.llvm_type,
.display_name = display_name,
};
try self.struct_types.put(mangled_name, si2);
try self.type_registry.put(mangled_name, .{ .struct_info = si2 });
_ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name });
@@ -1544,7 +1594,6 @@ pub const CodeGen = struct {
.max_payload_size = build.max_payload_size,
.payload_field_index = build.payload_field_index,
};
try self.tagged_enum_types.put(mangled_name, tei);
try self.type_registry.put(mangled_name, .{ .tagged_enum = tei });
_ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name });
@@ -1652,7 +1701,7 @@ pub const CodeGen = struct {
if (!sx_ty.isStruct()) return self.typeToLLVM(sx_ty);
const sname = self.resolveAlias(sx_ty.struct_type);
const info = self.struct_types.get(sname) orelse return self.typeToLLVM(sx_ty);
const info = self.lookupStructInfo(sname) orelse return self.typeToLLVM(sx_ty);
if (self.target_config.isAarch64()) {
return self.aarch64ParamABI(info);
@@ -1899,11 +1948,9 @@ pub const CodeGen = struct {
// Tagged enum with payloads
try self.registerTaggedEnum(ed);
const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
try self.type_aliases.put(qualified_u, 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.enum_types.put(qualified, ed.variant_names);
try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names });
_ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified });
if (ed.backing_type) |bt_node| {
@@ -1916,13 +1963,11 @@ pub const CodeGen = struct {
try self.registerStructType(sd);
// Register qualified alias so rl.Color resolves to Color
const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name });
try self.type_aliases.put(qualified_s, sd.name);
try self.type_registry.put(qualified_s, .{ .alias = sd.name });
},
.union_decl => |ud| {
try self.registerUnionType(ud);
const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ud.name });
try self.type_aliases.put(qualified_u, ud.name);
try self.type_registry.put(qualified_u, .{ .alias = ud.name });
},
.const_decl => |cd| {
@@ -1933,7 +1978,6 @@ pub const CodeGen = struct {
try self.registerLambdaAsFunction(qualified, cd.value.data.lambda);
} else if (cd.value.data == .type_expr) {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name });
try self.type_aliases.put(qualified, cd.value.data.type_expr.name);
try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name });
}
},
@@ -2019,8 +2063,11 @@ pub const CodeGen = struct {
const name_ptr = c.LLVMGetStructName(llvm_ty);
if (name_ptr != null) {
const name = std.mem.span(name_ptr);
if (self.struct_types.contains(name)) return .{ .struct_type = name };
if (self.tagged_enum_types.contains(name)) return .{ .union_type = name };
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
@@ -2277,11 +2324,29 @@ pub const CodeGen = struct {
const saved_named = self.named_values;
self.named_values = std.StringHashMap(NamedValue).init(self.allocator);
// Register with correct types (null return_type = void)
try self.registerFnDecl(fd, fd.name);
// 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;
// Generate body inline
const ret_sx_type = self.resolveType(fd.return_type);
// For arrow lambdas with inferred return type, build function manually
if (fd.is_arrow and fd.return_type == null) {
const ret_llvm_type = self.typeToLLVM(ret_sx_type);
var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty;
for (fd.params) |param| {
try param_llvm_types.append(self.allocator, self.typeToLLVM(self.resolveType(param.type_expr)));
}
const params_slice = try param_llvm_types.toOwnedSlice(self.allocator);
const fn_type = c.LLVMFunctionType(
ret_llvm_type,
if (params_slice.len > 0) params_slice.ptr else null,
@intCast(params_slice.len),
0,
);
const name_z2 = try self.allocator.dupeZ(u8, fd.name);
_ = c.LLVMAddFunction(self.module, name_z2.ptr, fn_type);
try self.function_return_types.put(fd.name, ret_sx_type);
} else {
try self.registerFnDecl(fd, fd.name);
}
self.current_return_type = ret_sx_type;
const name_z = try self.allocator.dupeZ(u8, fd.name);
const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse
@@ -2460,7 +2525,7 @@ pub const CodeGen = struct {
sx_ty = .{ .union_type = uname };
// C-style (untagged) union
if (self.union_types.get(uname)) |info| {
if (self.lookupUnionInfo(uname)) |info| {
const alloca = try self.buildNamedAlloca(info.llvm_type, vd.name);
if (vd.value == null) {
@@ -2652,6 +2717,25 @@ pub const CodeGen = struct {
return null;
}
// Local lambda: register as function, generate body, done
if (cd.value.data == .lambda) {
const saved_fn = self.current_function;
const saved_bb = self.getCurrentBlock();
const saved_ret = self.current_return_type;
const saved_named = self.named_values;
self.named_values = std.StringHashMap(NamedValue).init(self.allocator);
try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda);
try self.genLambdaBody(cd.name, cd.value.data.lambda);
self.named_values.deinit();
self.named_values = saved_named;
self.current_return_type = saved_ret;
self.current_function = saved_fn;
self.positionAt(saved_bb);
return null;
}
var sx_ty: Type = Type.s(64);
if (cd.type_annotation) |ta| {
@@ -2688,6 +2772,8 @@ pub const CodeGen = struct {
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);
@@ -2745,10 +2831,10 @@ pub const CodeGen = struct {
// Target must be an identifier
if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable");
const name = asgn.target.data.identifier.name;
const entry = self.named_values.get(name) orelse
self.global_mutable_vars.get(name) orelse {
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});
// Meta type reassignment: x = Vec4, x = f64, x = test
if (entry.ty == .meta_type and asgn.op == .assign) {
@@ -2789,7 +2875,7 @@ pub const CodeGen = struct {
// Tagged enum reassignment: s = .circle(3.14) or s = .none or s = fn_call()
if (entry.ty.isUnion() and asgn.op == .assign) {
if (self.tagged_enum_types.get(entry.ty.union_type)) |info| {
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);
@@ -2840,7 +2926,7 @@ pub const CodeGen = struct {
// C-style union field assignment
if (entry.ty.isUnion()) {
const uname = entry.ty.union_type;
if (self.union_types.get(uname)) |info| {
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);
@@ -2966,22 +3052,15 @@ pub const CodeGen = struct {
return self.buildStringSlice(ptr, self.constInt64(@intCast(content.len)));
},
.identifier => |ident| {
if (self.named_values.get(ident.name)) |entry| {
const llvm_ty = self.typeToLLVM(entry.ty);
return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "loadtmp");
}
// Fall back to comptime globals (lazy resolution)
if (self.comptime_globals.getPtr(ident.name)) |ct| {
if (!ct.is_resolved) {
try self.resolveComptimeGlobal(ct);
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"),
}
const llvm_ty = self.typeToLLVM(ct.ty);
return c.LLVMBuildLoad2(self.builder, llvm_ty, ct.global, "ct_load");
}
// Fall back to global mutable variables (e.g. function pointers from opengl.sx)
if (self.global_mutable_vars.get(ident.name)) |entry| {
const llvm_ty = self.typeToLLVM(entry.ty);
return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "global_load");
}
// Fall back to function name → function pointer value
{
@@ -3010,7 +3089,7 @@ pub const CodeGen = struct {
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.tagged_enum_types.get(resolved) orelse return self.emitError("unknown tagged enum type");
const info = self.lookupTaggedEnumInfo(resolved) orelse return self.emitError("unknown tagged enum type");
const tag_ty = self.getEnumLLVMType(resolved);
var lhs_val = try self.genExprAsType(binop.lhs, result_type);
@@ -3224,7 +3303,7 @@ pub const CodeGen = struct {
}
// &u.field where u is a C-style union — all fields at offset 0
if (entry.ty.isUnion()) {
if (self.union_types.get(entry.ty.union_type)) |info| {
if (self.lookupUnionInfo(entry.ty.union_type)) |info| {
if (self.findNameIndex(info.field_names, fa.field) != null) {
return entry.ptr;
}
@@ -3237,7 +3316,7 @@ pub const CodeGen = struct {
// &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.struct_types.get(pointee_name)) |info| {
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);
@@ -3338,7 +3417,6 @@ pub const CodeGen = struct {
hoisted.name = synthetic_name;
try self.registerTaggedEnum(hoisted);
} else {
try self.enum_types.put(synthetic_name, inline_ed.variant_names);
try self.type_registry.put(synthetic_name, .{ .plain_enum = inline_ed.variant_names });
_ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name });
if (inline_ed.backing_type) |bt_node| {
@@ -3387,7 +3465,6 @@ pub const CodeGen = struct {
.field_defaults = resolved_defaults,
.llvm_type = build.llvm_type,
};
try self.struct_types.put(sd.name, sinfo);
try self.type_registry.put(sd.name, .{ .struct_info = sinfo });
_ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name });
}
@@ -3424,7 +3501,6 @@ pub const CodeGen = struct {
.max_payload_size = layout.payload_size,
.payload_field_index = layout.payload_field_index,
};
try self.tagged_enum_types.put(ud.name, tei_layout);
try self.type_registry.put(ud.name, .{ .tagged_enum = tei_layout });
} else {
// Primitive backing type (e.g. enum u32 { ... })
@@ -3442,7 +3518,6 @@ pub const CodeGen = struct {
.max_payload_size = build.max_payload_size,
.payload_field_index = build.payload_field_index,
};
try self.tagged_enum_types.put(ud.name, tei_build);
try self.type_registry.put(ud.name, .{ .tagged_enum = tei_build });
}
@@ -3500,7 +3575,7 @@ pub const CodeGen = struct {
const resolved = self.resolveAlias(name);
if (Type.fromName(resolved) != null) return null;
// Must be a registered struct
if (self.struct_types.contains(resolved)) {
if (self.lookupStructInfo(resolved) != null) {
return try self.validateEnumLayout(ud.name, resolved);
}
}
@@ -3509,7 +3584,7 @@ pub const CodeGen = struct {
}
fn validateEnumLayout(self: *CodeGen, enum_name: []const u8, layout_name: []const u8) !EnumLayoutInfo {
const layout = self.struct_types.get(layout_name) orelse {
const layout = self.lookupStructInfo(layout_name) orelse {
return self.emitErrorFmt("enum '{s}': layout type '{s}' is not a registered struct", .{ enum_name, layout_name });
};
@@ -3607,7 +3682,7 @@ pub const CodeGen = struct {
// Check if this is an anonymous struct (name contains __anon_)
const sname = fty.struct_type;
if (std.mem.indexOf(u8, sname, ".__anon_") != null) {
if (self.struct_types.get(sname)) |sinfo| {
if (self.lookupStructInfo(sname)) |sinfo| {
for (sinfo.field_names, 0..) |sf_name, sf_idx| {
try promoted.put(sf_name, .{
.struct_name = sname,
@@ -3627,7 +3702,6 @@ pub const CodeGen = struct {
.total_size = max_size,
.promoted_fields = promoted,
};
try self.union_types.put(ud.name, uinfo);
try self.type_registry.put(ud.name, .{ .union_info = uinfo });
// Note: C-style unions are not registered with the Any type system.
// They can't be meaningfully printed as a whole — access individual fields instead.
@@ -3901,7 +3975,7 @@ pub const CodeGen = struct {
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.struct_types.get(sname)) |si| {
if (self.lookupStructInfo(sname)) |si| {
return c.LLVMBuildLoad2(self.builder, si.llvm_type, alloca, "struct_val");
}
return alloca;
@@ -3986,13 +4060,13 @@ pub const CodeGen = struct {
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.type_aliases.get(src_ty.struct_type)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or
(if (self.type_aliases.get(pointee_name)) |alias| std.mem.eql(u8, alias, src_ty.struct_type) else false)
(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.type_aliases.get(uname)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or
(if (self.type_aliases.get(pointee_name)) |alias| std.mem.eql(u8, alias, uname) else false);
(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
@@ -4012,8 +4086,8 @@ pub const CodeGen = struct {
// 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.struct_types.get(src_ty.struct_type) orelse
self.struct_types.get(self.resolveAlias(src_ty.struct_type));
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");
}
@@ -4083,7 +4157,7 @@ pub const CodeGen = struct {
}
if (target_ty.isStruct()) {
const sname = target_ty.struct_type;
if (self.struct_types.get(sname)) |info| {
if (self.lookupStructInfo(sname)) |info| {
return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_struct");
}
}
@@ -4095,7 +4169,7 @@ pub const CodeGen = struct {
}
if (target_ty.isUnion()) {
const uname = target_ty.union_type;
if (self.tagged_enum_types.get(uname)) |info| {
if (self.lookupTaggedEnumInfo(uname)) |info| {
return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_union");
}
}
@@ -4144,7 +4218,7 @@ pub const CodeGen = struct {
// Union → int: extract the tag field (index 0)
if (src_ty.isUnion() and target_ty.isInt()) {
const uname = src_ty.union_type;
if (self.tagged_enum_types.get(uname)) |info| {
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");
@@ -4333,7 +4407,7 @@ pub const CodeGen = struct {
return c.LLVMConstInt(i64_ty, info.field_names.len, 0);
}
if (ty.isEnum()) {
const variants = self.enum_types.get(ty.enum_type) orelse
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);
}
@@ -4359,7 +4433,7 @@ pub const CodeGen = struct {
const info = try self.getStructInfo(ty.struct_type);
break :blk .{ info.field_names, ty.struct_type };
} else if (ty.isEnum()) blk: {
const variants = self.enum_types.get(ty.enum_type) orelse
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: {
@@ -4558,7 +4632,7 @@ pub const CodeGen = struct {
}
const enum_name = ty.enum_type;
const values = self.enum_variant_values.get(enum_name);
const variants = self.enum_types.get(enum_name) orelse return try self.genExpr(call_node.args[1]);
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]);
@@ -4587,6 +4661,57 @@ pub const CodeGen = struct {
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);
@@ -4598,7 +4723,7 @@ pub const CodeGen = struct {
return c.LLVMConstInt(i64_type, 0, 0);
}
const values = self.enum_variant_values.get(enum_name);
const variants = self.enum_types.get(enum_name) orelse return try self.genExpr(call_node.args[1]);
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]);
@@ -4736,7 +4861,7 @@ pub const CodeGen = struct {
if (entry.ty.isUnion()) {
const uname = entry.ty.union_type;
// C-style (untagged) union: bitcast pointer and load
if (self.union_types.get(uname)) |info| {
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");
@@ -5059,8 +5184,11 @@ pub const CodeGen = struct {
const resolved_type: ?Type = blk: {
if (fa.object.data == .identifier) {
const name = self.resolveAlias(fa.object.data.identifier.name);
if (self.tagged_enum_types.contains(name)) break :blk .{ .union_type = name };
if (self.struct_types.contains(name)) break :blk .{ .struct_type = 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;
@@ -5179,11 +5307,12 @@ pub const CodeGen = struct {
}
// Function pointer indirect call: callee is a variable with function_type
if (callee_fn == null) {
const fp_entry = if (self.named_values.get(callee_name)) |e| e
else self.global_mutable_vars.get(callee_name);
if (fp_entry) |entry| {
if (entry.ty.isFunctionType()) {
return self.genIndirectCall(entry, call_node);
if (self.lookupValue(callee_name)) |v| {
const entry = v.asNamedValue();
if (entry) |e| {
if (e.ty.isFunctionType()) {
return self.genIndirectCall(e, call_node);
}
}
}
return self.emitErrorFmt("undefined function '{s}'", .{callee_name});
@@ -5416,10 +5545,7 @@ pub const CodeGen = struct {
// 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.struct_types.contains(name) and
!self.enum_types.contains(name) and
!self.tagged_enum_types.contains(name) and
!self.type_aliases.contains(name))
!self.type_registry.contains(name))
{
return self.genGenericCallWithRuntimeDispatch(template, call_node, match_tags);
}
@@ -5492,7 +5618,7 @@ pub const CodeGen = struct {
arg_ty.struct_type
else
"";
if (self.struct_types.get(struct_name)) |info| {
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
@@ -6183,6 +6309,7 @@ pub const CodeGen = struct {
// 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);
@@ -6216,22 +6343,26 @@ pub const CodeGen = struct {
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;
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;
// Increment it_index
// Fall through to increment block
const current_bb = self.getCurrentBlock();
if (c.LLVMGetBasicBlockTerminator(current_bb) == null) {
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.br(incr_bb);
}
// Increment it_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();
@@ -6241,7 +6372,7 @@ pub const CodeGen = struct {
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.enum_types.get(enum_type_name) orelse return c.LLVMConstInt(enum_ty, 0, 0);
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)) {
@@ -6281,14 +6412,14 @@ pub const CodeGen = struct {
const subject_val: c.LLVMValueRef = if (union_name != null) blk: {
// Union: load tag from field 0 of the alloca
const entry = self.named_values.get(match.subject.data.identifier.name).?;
const info = self.tagged_enum_types.get(union_name.?).?;
const info = self.lookupTaggedEnumInfo(union_name.?).?;
break :blk self.loadStructField(info.llvm_type, entry.ptr, 0, self.getEnumLLVMType(union_name.?));
} else try self.genExpr(match.subject);
const variants: ?[]const []const u8 = if (union_name) |un|
(if (self.tagged_enum_types.get(un)) |info| info.variant_names else null)
(if (self.lookupTaggedEnumInfo(un)) |info| info.variant_names else null)
else if (enum_name) |en|
self.enum_types.get(en)
self.lookupEnumVariants(en)
else
null;
@@ -6372,7 +6503,7 @@ pub const CodeGen = struct {
// Payload capture: bind variant payload as a local variable
if (arm.capture) |cap_name| {
if (union_name) |un| {
const uinfo = self.tagged_enum_types.get(un).?;
const uinfo = self.lookupTaggedEnumInfo(un).?;
const pat = arm.pattern.?;
if (pat.data == .enum_literal) {
const vname = pat.data.enum_literal.name;
@@ -6515,14 +6646,13 @@ pub const CodeGen = struct {
return tags;
}
// Named type (struct/enum/union) — get dynamic ID
const sx_type: Type = if (self.struct_types.contains(name))
.{ .struct_type = name }
else if (self.enum_types.contains(name))
.{ .enum_type = name }
else if (self.tagged_enum_types.contains(name))
.{ .union_type = name }
else
.{ .struct_type = name }; // fallback
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;
@@ -6714,7 +6844,7 @@ pub const CodeGen = struct {
/// 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.struct_types.get(resolved)) |info| return info.display_name orelse resolved;
if (self.lookupStructInfo(resolved)) |info| return info.display_name orelse resolved;
if (self.resolveTypeFromName(name) != null) return resolved;
return null;
}
@@ -6752,7 +6882,7 @@ pub const CodeGen = struct {
}
}
// Type aliases
if (self.type_aliases.get(name)) |target| return self.resolveTypeFromName(target);
if (self.lookupAlias(name)) |target| return self.resolveTypeFromName(target);
return null;
}
@@ -6776,9 +6906,7 @@ pub const CodeGen = struct {
},
.chained_comparison => return .boolean,
.identifier => |ident| {
if (self.named_values.get(ident.name)) |entry| return entry.ty;
if (self.comptime_globals.get(ident.name)) |ct| return ct.ty;
if (self.global_mutable_vars.get(ident.name)) |entry| return entry.ty;
if (self.lookupValue(ident.name)) |v| return v.ty();
return Type.s(64);
},
.if_expr => |ie| {
@@ -6808,7 +6936,7 @@ pub const CodeGen = struct {
const obj_ty = blk: {
if (fa.object.data == .identifier) {
const name = self.resolveAlias(fa.object.data.identifier.name);
if (self.tagged_enum_types.contains(name)) break :blk Type{ .union_type = name };
if (self.lookupTaggedEnumInfo(name) != null) break :blk Type{ .union_type = name };
}
const ty = self.resolveType(fa.object);
if (ty.isUnion()) break :blk ty;
@@ -6921,11 +7049,9 @@ pub const CodeGen = struct {
}
// Check if callee is a variable with function pointer type
{
const fp_entry = if (self.named_values.get(callee_name)) |e| e
else self.global_mutable_vars.get(callee_name);
if (fp_entry) |entry| {
if (entry.ty.isFunctionType()) {
return entry.ty.function_type.return_type.*;
if (self.lookupValue(callee_name)) |v| {
if (v.ty().isFunctionType()) {
return v.ty().function_type.return_type.*;
}
}
}
@@ -6969,14 +7095,14 @@ pub const CodeGen = struct {
return obj_ty.vectorElementType() orelse Type.s(64);
}
if (obj_ty.isStruct()) {
if (self.struct_types.get(obj_ty.struct_type)) |info| {
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.union_types.get(obj_ty.union_type)) |info| {
if (self.lookupUnionInfo(obj_ty.union_type)) |info| {
if (self.findNameIndex(info.field_names, fa.field)) |idx| {
return info.field_types[idx];
}
@@ -6984,7 +7110,7 @@ pub const CodeGen = struct {
return pf.field_type;
}
}
if (self.tagged_enum_types.get(obj_ty.union_type)) |info| {
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];

View File

@@ -745,6 +745,7 @@ pub const Parser = struct {
}
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, or #foreign marker
var is_arrow = false;
const body = if (self.current.tag == .hash_builtin) blk: {
const bi_start = self.current.loc.start;
self.advance();
@@ -756,6 +757,7 @@ pub const Parser = struct {
try self.expect(.semicolon);
break :blk try self.createNode(fi_start, .{ .foreign_expr = {} });
} else if (self.current.tag == .fat_arrow) blk: {
is_arrow = true;
self.advance();
const expr = try self.parseExpr();
try self.expect(.semicolon);
@@ -774,6 +776,7 @@ pub const Parser = struct {
.return_type = return_type,
.body = body,
.type_params = type_params,
.is_arrow = is_arrow,
} });
}