This commit is contained in:
agra
2026-02-11 20:41:43 +02:00
parent 94b0296fd5
commit 9d96f05d3b
13 changed files with 460 additions and 70 deletions

View File

@@ -91,6 +91,10 @@ pub const CodeGen = struct {
current_match_tags: ?[]const u64 = null,
// Functions deferred to compile after all types are registered (e.g. any_to_string)
deferred_fn_bodies: std.ArrayList(DeferredFn),
// Libraries to link against (from #library directives)
foreign_libraries: std.ArrayList([]const u8),
// Set of foreign function names (for ABI lowering at call sites)
foreign_fns: std.StringHashMap(void),
const DeferredFn = struct {
fd: ast.FnDecl,
@@ -194,6 +198,8 @@ pub const CodeGen = struct {
.any_type_id_map = std.StringHashMap(u64).init(allocator),
.any_type_entries = std.StringHashMap(AnyTypeEntry).init(allocator),
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
.foreign_libraries = std.ArrayList([]const u8).empty,
.foreign_fns = std.StringHashMap(void).init(allocator),
};
}
@@ -214,6 +220,8 @@ pub const CodeGen = struct {
self.any_type_id_map.deinit();
self.any_type_entries.deinit();
self.deferred_fn_bodies.deinit(self.allocator);
self.foreign_libraries.deinit(self.allocator);
self.foreign_fns.deinit();
c.LLVMDisposeBuilder(self.builder);
c.LLVMDisposeModule(self.module);
c.LLVMContextDispose(self.context);
@@ -229,6 +237,21 @@ pub const CodeGen = struct {
return error.CodeGenError;
}
/// 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);
}
pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef {
return switch (ty) {
.signed => |w| c.LLVMIntTypeInContext(self.context, w),
@@ -347,11 +370,17 @@ pub const CodeGen = struct {
/// 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, ty: Type) !c.LLVMValueRef {
fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, in_ty: Type) !c.LLVMValueRef {
const any_ty = self.getAnyStructType();
const i64_ty = c.LLVMInt64TypeInContext(self.context);
const undef = c.LLVMGetUndef(any_ty);
// []u8 boxes as string (same repr, same Any tag)
const ty: Type = if (in_ty.isSlice() and std.mem.eql(u8, in_ty.slice_type.element_name, "u8"))
.string_type
else
in_ty;
// Determine tag
const tag: u64 = switch (ty) {
.void_type => ANY_TAG_VOID,
@@ -394,7 +423,7 @@ pub const CodeGen = struct {
.f64 => c.LLVMBuildBitCast(self.builder, val, i64_ty, "any_f64"),
.string_type => blk: {
// String is {ptr, i32} — store to alloca, pass alloca as i64
const str_alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_str_tmp");
const str_alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_str_tmp");
_ = c.LLVMBuildStore(self.builder, val, str_alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, str_alloca, i64_ty, "any_str");
},
@@ -402,7 +431,7 @@ pub const CodeGen = struct {
// Struct — store to alloca, pass pointer as i64
const info = self.struct_types.get(sname) orelse
return c.LLVMGetUndef(any_ty);
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_struct_tmp");
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "any_struct_tmp");
_ = c.LLVMBuildStore(self.builder, val, alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_struct");
},
@@ -414,20 +443,20 @@ pub const CodeGen = struct {
// Union — store to alloca, pass pointer as i64
const info = self.union_types.get(uname) orelse
return c.LLVMGetUndef(any_ty);
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_union_tmp");
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "any_union_tmp");
_ = c.LLVMBuildStore(self.builder, val, alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_union");
},
.vector_type, .array_type => blk: {
// Vector/Array — store to alloca, pass pointer as i64
const llvm_ty = self.typeToLLVM(ty);
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, "any_vec_tmp");
const alloca = self.buildEntryBlockAlloca(llvm_ty, "any_vec_tmp");
_ = c.LLVMBuildStore(self.builder, val, alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_vec");
},
.slice_type => blk: {
// Slice {ptr, i32} — store to alloca, pass pointer as i64
const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_slice_tmp");
const alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_slice_tmp");
_ = c.LLVMBuildStore(self.builder, val, alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_slice");
},
@@ -435,7 +464,7 @@ pub const CodeGen = struct {
.meta_type => |mt| blk: {
// Meta type: wrap raw char ptr in string slice {ptr, len} for extraction
const str_slice = self.buildStringSlice(val, mt.name.len);
const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_type_tmp");
const alloca = self.buildEntryBlockAlloca(self.getStringStructType(), "any_type_tmp");
_ = c.LLVMBuildStore(self.builder, str_slice, alloca);
break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_type");
},
@@ -542,6 +571,9 @@ pub const CodeGen = struct {
.fn_decl => |fd| {
if (fd.body.data == .builtin_expr) {
try self.builtin_functions.put(fd.name, {});
} else if (fd.body.data == .foreign_expr) {
// External C function — register LLVM declaration (no body)
try self.registerFnDecl(fd);
} else if (fd.type_params.len > 0) {
try self.generic_templates.put(fd.name, .{ .fd = fd });
} else {
@@ -549,6 +581,9 @@ pub const CodeGen = struct {
}
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| {
try self.enum_types.put(ed.name, ed.variants);
_ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name });
@@ -642,8 +677,8 @@ pub const CodeGen = struct {
for (root.data.root.decls) |decl| {
switch (decl.data) {
.fn_decl => |fd| {
if (fd.body.data == .builtin_expr) {
// skip
if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) {
// skip — no body to generate
} else if (fd.type_params.len == 0) {
if (shouldDeferFnBody(fd)) {
try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name });
@@ -1100,6 +1135,10 @@ pub const CodeGen = struct {
}
fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8) !c.LLVMTypeRef {
return self.buildFnTypeEx(params, return_type, name, false);
}
fn buildFnTypeEx(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)
@@ -1116,7 +1155,14 @@ pub const CodeGen = struct {
} 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 });
try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty));
// Foreign functions: apply C ABI lowering
if (is_foreign and sx_ty == .string_type) {
try param_llvm_types.append(self.allocator, c.LLVMPointerTypeInContext(self.context, 0));
} else if (is_foreign and sx_ty.isStruct()) {
try param_llvm_types.append(self.allocator, self.getForeignParamABIType(sx_ty));
} else {
try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty));
}
}
}
const params_slice = try param_llvm_types.toOwnedSlice(self.allocator);
@@ -1129,14 +1175,82 @@ pub const CodeGen = struct {
);
}
/// For foreign (C ABI) functions on ARM64, struct parameters must be lowered
/// to their ABI-equivalent types. LLVM does NOT do this automatically.
/// - HFA (1-4 same float/double fields): [N x float/double]
/// - Non-HFA ≤ 8 bytes: i64
/// - Non-HFA 9-16 bytes: [2 x i64]
fn getForeignParamABIType(self: *CodeGen, sx_ty: Type) c.LLVMTypeRef {
const is_aarch64 = comptime @import("builtin").cpu.arch == .aarch64;
if (!is_aarch64) return self.typeToLLVM(sx_ty);
if (!sx_ty.isStruct()) return self.typeToLLVM(sx_ty);
const sname = self.type_aliases.get(sx_ty.struct_type) orelse sx_ty.struct_type;
const info = self.struct_types.get(sname) orelse return self.typeToLLVM(sx_ty);
// 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 (!std.meta.eql(ft, first)) {
all_same = false;
break;
}
}
if (all_same) {
const elem_ty = if (first == .f32)
c.LLVMFloatTypeInContext(self.context)
else
c.LLVMDoubleTypeInContext(self.context);
return c.LLVMArrayType2(elem_ty, @intCast(field_types.len));
}
}
}
// Non-HFA: pack into integer registers
const data_layout = c.LLVMGetModuleDataLayout(self.module);
const size = c.LLVMStoreSizeOfType(data_layout, info.llvm_type);
if (size <= 8) return c.LLVMInt64TypeInContext(self.context);
if (size <= 16) return c.LLVMArrayType2(c.LLVMInt64TypeInContext(self.context), 2);
// > 16 bytes: pass by pointer (indirect) — not yet handled, fall back to struct type
return info.llvm_type;
}
/// 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 data_layout = c.LLVMGetModuleDataLayout(self.module);
const struct_size = c.LLVMStoreSizeOfType(data_layout, struct_ty);
const abi_size = c.LLVMStoreSizeOfType(data_layout, 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");
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(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) !void {
return self.registerFnDeclAs(fd, fd.name);
}
fn registerFnDeclAs(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void {
const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name);
const is_foreign = fd.body.data == .foreign_expr;
const fn_type = try self.buildFnTypeEx(fd.params, fd.return_type, fd.name, is_foreign);
const name_z = try self.allocator.dupeZ(u8, llvm_name);
_ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type);
// Track foreign functions for ABI lowering at call sites
if (is_foreign) try self.foreign_fns.put(llvm_name, {});
// Track resolved parameter types for accurate call-site conversion
var param_types = std.ArrayList(Type).empty;
for (fd.params) |param| {
@@ -1173,7 +1287,19 @@ pub const CodeGen = struct {
continue;
}
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
if (fd.type_params.len > 0) {
if (fd.body.data == .foreign_expr) {
// External C function in namespace — register LLVM declaration with C name only
try self.registerFnDeclAs(fd, fd.name);
// Also track qualified name as foreign for ABI lowering at call sites
try self.foreign_fns.put(qualified, {});
// Store param types under qualified name so call-site type resolution works
var param_types = std.ArrayList(Type).empty;
for (fd.params) |param| {
if (param.is_comptime) continue;
try param_types.append(self.allocator, self.resolveType(param.type_expr));
}
try self.fn_param_types.put(qualified, try param_types.toOwnedSlice(self.allocator));
} else if (fd.type_params.len > 0) {
try self.generic_templates.put(qualified, .{ .fd = fd });
} else {
try self.registerFnDeclAs(fd, qualified);
@@ -1183,8 +1309,17 @@ pub const CodeGen = struct {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name });
try self.enum_types.put(qualified, ed.variants);
},
.struct_decl => |sd| try self.registerStructType(sd),
.union_decl => |ud| try self.registerUnionType(ud),
.struct_decl => |sd| {
try self.registerStructType(sd);
// Register qualified alias so rl.Color resolves to Color
const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name });
try self.type_aliases.put(qualified_s, 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);
},
.const_decl => |cd| {
if (cd.value.data == .builtin_expr) {
// #builtin constant in namespace — skip codegen
@@ -1196,6 +1331,9 @@ pub const CodeGen = struct {
try self.type_aliases.put(qualified, cd.value.data.type_expr.name);
}
},
.library_decl => |ld| {
try self.foreign_libraries.append(self.allocator, ld.lib_name);
},
else => {},
}
}
@@ -1209,8 +1347,8 @@ pub const CodeGen = struct {
for (ns.decls) |decl| {
switch (decl.data) {
.fn_decl => |fd| {
if (fd.body.data == .builtin_expr) {
// skip
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)) {
@@ -1636,7 +1774,7 @@ pub const CodeGen = struct {
const type_name = try self.allocator.dupeZ(u8, raw_name);
const name_z = try self.allocator.dupeZ(u8, vd.name);
const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0);
const alloca = c.LLVMBuildAlloca(self.builder, ptr_ty, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(ptr_ty, name_z.ptr);
const str_val = c.LLVMBuildGlobalStringPtr(self.builder, type_name.ptr, "type_name");
_ = c.LLVMBuildStore(self.builder, str_val, alloca);
try self.saveShadowed(vd.name);
@@ -1676,7 +1814,7 @@ pub const CodeGen = struct {
sx_ty = .{ .struct_type = sname };
const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname});
const name_z = try self.allocator.dupeZ(u8, vd.name);
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
if (vd.value == null) {
// Default-init: per-field defaults or zero
@@ -1711,7 +1849,7 @@ pub const CodeGen = struct {
sx_ty = .{ .union_type = uname };
const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname});
const name_z = try self.allocator.dupeZ(u8, vd.name);
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
if (vd.value == null) {
// Zero-init: tag=0, payload zeroed
@@ -1756,7 +1894,7 @@ pub const CodeGen = struct {
const arr_info = sx_ty.array_type;
const llvm_arr_ty = self.typeToLLVM(sx_ty);
const arr_name_z = try self.allocator.dupeZ(u8, vd.name);
const arr_alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, arr_name_z.ptr);
const arr_alloca = self.buildEntryBlockAlloca(llvm_arr_ty, arr_name_z.ptr);
if (vd.value == null) {
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_arr_ty), arr_alloca);
@@ -1798,7 +1936,7 @@ pub const CodeGen = struct {
if (sx_ty.isVector()) {
const llvm_vec_ty = self.typeToLLVM(sx_ty);
const vec_name_z = try self.allocator.dupeZ(u8, vd.name);
const vec_alloca = c.LLVMBuildAlloca(self.builder, llvm_vec_ty, vec_name_z.ptr);
const vec_alloca = self.buildEntryBlockAlloca(llvm_vec_ty, vec_name_z.ptr);
if (vd.value == null) {
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_vec_ty), vec_alloca);
@@ -1826,7 +1964,7 @@ pub const CodeGen = struct {
// Non-struct types
const llvm_ty = self.typeToLLVM(sx_ty);
const name_z = try self.allocator.dupeZ(u8, vd.name);
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
if (vd.value == null) {
// Default-init: zero
@@ -1912,7 +2050,7 @@ pub const CodeGen = struct {
const llvm_ty = self.typeToLLVM(sx_ty);
const name_z = try self.allocator.dupeZ(u8, cd.name);
const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
_ = c.LLVMBuildStore(self.builder, init_val, alloca);
try self.saveShadowed(cd.name);
try self.named_values.put(cd.name, .{ .ptr = alloca, .ty = sx_ty });
@@ -2571,7 +2709,7 @@ pub const CodeGen = struct {
const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ ul.variant_name, resolved_name });
// Alloca union
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_tmp");
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_tmp");
const i64_ty = c.LLVMInt64TypeInContext(self.context);
// Store tag (field 0)
@@ -2613,7 +2751,7 @@ pub const CodeGen = struct {
// Alloca the struct and default-init all fields (zero or declared defaults)
const name_z = try self.allocator.dupeZ(u8, sname);
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr);
const alloca = self.buildEntryBlockAlloca(info.llvm_type, name_z.ptr);
try self.genStructDefaultInit(alloca, info);
// Determine if this is named or positional mode
@@ -2671,7 +2809,7 @@ pub const CodeGen = struct {
const arr_info = arr_ty.array_type;
const elem_sx_ty = Type.fromName(arr_info.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{arr_info.element_name});
const llvm_arr_ty = self.typeToLLVM(arr_ty);
const alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, "arr");
const alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "arr");
const len = @min(al.elements.len, arr_info.length);
for (0..len) |i| {
@@ -2694,7 +2832,7 @@ pub const CodeGen = struct {
// 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 = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, "slice_backing");
const arr_alloca = self.buildEntryBlockAlloca(llvm_arr_ty, "slice_backing");
// Fill elements
for (0..n) |i| {
@@ -2758,6 +2896,13 @@ pub const CodeGen = struct {
return self.convertValue(val, src_ty, target_ty);
}
// String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping)
if (node.data == .string_literal and target_ty.isPointer()) {
const unescaped = try unescapeString(self.allocator, node.data.string_literal.raw);
const str_z = try self.allocator.dupeZ(u8, unescaped);
return c.LLVMBuildGlobalStringPtr(self.builder, str_z.ptr, "str");
}
// Enum literal assigned to union type: construct tag-only (void variant) union
if (node.data == .enum_literal and target_ty.isUnion()) {
const ul = ast.UnionLiteral{
@@ -2799,7 +2944,7 @@ pub const CodeGen = struct {
const variant_ty = info.variant_types[idx];
// Alloca union, store tag
const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_lit");
const alloca = self.buildEntryBlockAlloca(info.llvm_type, "union_lit");
const i32_ty = c.LLVMInt32TypeInContext(self.context);
const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 0, "tag");
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, idx, 0), tag_gep);
@@ -2826,7 +2971,13 @@ pub const CodeGen = struct {
// Struct literal targeting struct type: pass struct name context
if (node.data == .struct_literal and target_ty.isStruct()) {
return self.genStructLiteral(node.data.struct_literal, target_ty.struct_type);
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.type_aliases.get(target_ty.struct_type) orelse target_ty.struct_type;
if (self.struct_types.get(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
@@ -2893,9 +3044,20 @@ pub const CodeGen = struct {
}
}
const val = try self.genExpr(node);
var val = try self.genExpr(node);
const src_ty = self.inferType(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.struct_types.get(src_ty.struct_type) orelse
self.struct_types.get(self.type_aliases.get(src_ty.struct_type) orelse 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");
@@ -2925,6 +3087,13 @@ pub const CodeGen = struct {
// Same type → return as-is
if (std.meta.eql(src_ty, target_ty)) return val;
// string <-> []u8: identical LLVM type {ptr, i64}, no conversion needed
if ((src_ty == .string_type and target_ty.isSlice() and
std.mem.eql(u8, target_ty.slice_type.element_name, "u8")) or
(src_ty.isSlice() and std.mem.eql(u8, src_ty.slice_type.element_name, "u8") and
target_ty == .string_type))
return val;
const target_llvm = self.typeToLLVM(target_ty);
// Any → concrete type: extract the i64 value and convert
@@ -3014,7 +3183,7 @@ pub const CodeGen = struct {
if (src_ty.isUnion() and target_ty.isInt()) {
const uname = src_ty.union_type;
if (self.union_types.get(uname)) |info| {
const tmp = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_cast");
const tmp = self.buildEntryBlockAlloca(info.llvm_type, "union_cast");
_ = c.LLVMBuildStore(self.builder, val, tmp);
const tag_ptr = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, tmp, 0, "tag_ptr");
const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_ptr, "tag_val");
@@ -3238,7 +3407,7 @@ pub const CodeGen = struct {
const uinfo = self.union_types.get(val_ty.union_type) orelse
return self.emitErrorFmt("unknown union type '{s}'", .{val_ty.union_type});
const union_alloca = c.LLVMBuildAlloca(self.builder, uinfo.llvm_type, "fv_union");
const union_alloca = self.buildEntryBlockAlloca(uinfo.llvm_type, "fv_union");
_ = c.LLVMBuildStore(self.builder, val, union_alloca);
// Read tag (field 0)
@@ -3313,7 +3482,7 @@ pub const CodeGen = struct {
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 = c.LLVMBuildAlloca(self.builder, arr_llvm_ty, "fv_arr");
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{
@@ -3337,7 +3506,7 @@ pub const CodeGen = struct {
const n = info.field_names.len;
// Store struct to alloca BEFORE the switch (switch is a terminator)
const struct_alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "fv_struct");
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
@@ -3945,6 +4114,14 @@ pub const CodeGen = struct {
const name_z = try self.allocator.dupeZ(u8, callee_name);
var callee_fn = c.LLVMGetNamedFunction(self.module, name_z.ptr);
// 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 ..];
const base_z = try self.allocator.dupeZ(u8, base_name);
callee_fn = c.LLVMGetNamedFunction(self.module, base_z.ptr);
}
}
// Intra-namespace fallback: try qualified name
if (callee_fn == null) {
if (self.current_namespace) |ns| {
@@ -4024,7 +4201,7 @@ pub const CodeGen = struct {
} 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 = c.LLVMBuildAlloca(self.builder, arr_ty, "varargs_arr");
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: {
@@ -4068,7 +4245,34 @@ pub const CodeGen = struct {
stored_param_types.?[i]
else
self.llvmTypeToSxType(param_llvm_types[i]);
try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty));
// For #foreign functions, [:0]u8 params have LLVM type ptr
const arg_ty = self.inferType(arg);
const llvm_param_is_ptr = (i < num_params and
c.LLVMGetTypeKind(param_llvm_types[i]) == c.LLVMPointerTypeKind);
const ptr_ty = Type{ .pointer_type = .{ .pointee_name = "u8" } };
var val = if (llvm_param_is_ptr and arg.data == .string_literal) blk: {
// String literal → pointer: produce raw ptr directly (context-dependent)
break :blk try self.genExprAsType(arg, ptr_ty);
} else if (llvm_param_is_ptr and arg_ty == .string_type) blk: {
// String variable → pointer: extract .ptr from {ptr, len}
const str_val = try self.genExpr(arg);
break :blk c.LLVMBuildExtractValue(self.builder, 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));
}
@@ -4886,9 +5090,9 @@ pub const CodeGen = struct {
const elem_llvm_ty = self.typeToLLVM(elem_ty);
// Allocate it_index (s64) and it (element type)
const idx_alloca = c.LLVMBuildAlloca(self.builder, i64_type, "it_index");
const idx_alloca = self.buildEntryBlockAlloca(i64_type, "it_index");
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i64_type, 0, 0), idx_alloca);
const it_alloca = c.LLVMBuildAlloca(self.builder, elem_llvm_ty, "it");
const it_alloca = self.buildEntryBlockAlloca(elem_llvm_ty, "it");
// Push scope and bind it, it_index
try self.pushScope();
@@ -5602,7 +5806,7 @@ pub const CodeGen = struct {
}
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 .string_type;
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);
@@ -5654,7 +5858,7 @@ pub const CodeGen = struct {
},
.slice_expr => |se| {
const obj_ty = self.inferType(se.object);
if (obj_ty == .string_type) return .string_type;
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;
@@ -5726,9 +5930,23 @@ pub const CodeGen = struct {
}
}
pub fn link(io: std.Io, output_obj: []const u8, output_bin: []const u8) !void {
pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, output_bin: []const u8, libraries: []const []const u8) !void {
var argv = std.ArrayList([]const u8).empty;
try argv.appendSlice(allocator, &.{ "cc", output_obj, "-o", output_bin });
if (libraries.len > 0) {
// Add Homebrew library path on macOS
try argv.append(allocator, "-L/opt/homebrew/lib");
for (libraries) |lib| {
const flag = try std.fmt.allocPrint(allocator, "-l{s}", .{lib});
try argv.append(allocator, flag);
}
}
const argv_slice = try argv.toOwnedSlice(allocator);
var child = std.process.spawn(io, .{
.argv = &.{ "cc", output_obj, "-o", output_bin },
.argv = argv_slice,
}) catch return error.LinkError;
const result = child.wait(io) catch return error.LinkError;
if (result != .exited) return error.LinkError;