This commit is contained in:
agra
2026-03-05 16:20:36 +02:00
parent 22bc2439ce
commit f9dda972d2
36 changed files with 1063 additions and 7 deletions

View File

@@ -102,6 +102,8 @@ pub const Root = struct {
decls: []const *Node,
};
pub const CallingConvention = enum { default, c };
pub const FnDecl = struct {
name: []const u8,
params: []const Param,
@@ -109,6 +111,7 @@ pub const FnDecl = struct {
body: *Node,
type_params: []const StructTypeParam = &.{},
is_arrow: bool = false,
call_conv: CallingConvention = .default,
};
pub const Param = struct {
@@ -323,6 +326,7 @@ pub const Lambda = struct {
return_type: ?*Node,
body: *Node,
type_params: []const StructTypeParam = &.{},
call_conv: CallingConvention = .default,
};
pub const TypeExpr = struct {

View File

@@ -282,6 +282,7 @@ pub const LLVMEmitter = struct {
.float => |v| c.LLVMConstReal(llvm_ty, v),
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
else => c.LLVMConstNull(llvm_ty),
};
@@ -354,15 +355,16 @@ pub const LLVMEmitter = struct {
// main always returns i32 at the LLVM level (JIT expects it)
const raw_ret_ty = self.toLLVMType(func.ret);
const ret_ty = if (is_main) self.cached_i32 else if (func.is_extern) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty;
const needs_c_abi = func.is_extern or func.call_conv == .c;
const ret_ty = if (is_main) self.cached_i32 else if (needs_c_abi) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty;
// Build parameter types — apply C ABI coercion for foreign (extern) functions
// Build parameter types — apply C ABI coercion for foreign/callconv(.c) functions
const param_count: c_uint = @intCast(func.params.len);
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
defer self.alloc.free(param_types);
for (func.params, 0..) |param, j| {
const llvm_ty = self.toLLVMType(param.ty);
param_types[j] = if (func.is_extern) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
param_types[j] = if (needs_c_abi) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
}
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
@@ -378,6 +380,11 @@ pub const LLVMEmitter = struct {
.private => c.LLVMSetLinkage(llvm_func, c.LLVMPrivateLinkage),
}
// Set calling convention
if (func.call_conv == .c) {
c.LLVMSetFunctionCallConv(llvm_func, c.LLVMCCallConv);
}
// Add frame-pointer and nounwind attributes for correct ARM64 codegen
{
const fp_kind = "frame-pointer";
@@ -2840,6 +2847,23 @@ pub const LLVMEmitter = struct {
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
}
fn emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
const elem_ty = c.LLVMGetElementType(llvm_ty);
const n: c_uint = @intCast(agg.len);
const vals = self.alloc.alloc(c.LLVMValueRef, agg.len) catch return c.LLVMConstNull(llvm_ty);
defer self.alloc.free(vals);
for (agg, 0..) |cv, i| {
vals[i] = switch (cv) {
.int => |v| c.LLVMConstInt(elem_ty, @bitCast(v), 1),
.float => |v| c.LLVMConstReal(elem_ty, v),
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
else => c.LLVMConstNull(elem_ty),
};
}
return c.LLVMConstArray(elem_ty, vals.ptr, n);
}
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
// LLVMBuildGlobalStringPtr needs a null-terminated C string
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;

View File

@@ -410,6 +410,7 @@ pub const Function = struct {
is_extern: bool = false,
is_comptime: bool = false,
linkage: Linkage = .internal,
call_conv: CallingConvention = .default,
pub const Param = struct {
name: StringId,
@@ -422,6 +423,11 @@ pub const Function = struct {
private,
};
pub const CallingConvention = enum {
default,
c,
};
pub fn init(name: StringId, params: []const Param, ret: TypeId) Function {
return .{
.name = name,

View File

@@ -452,6 +452,7 @@ pub const Lowering = struct {
.bool_literal => |bl| .{ .boolean = bl.value },
.float_literal => |fl| .{ .float = fl.value },
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
.array_literal => |al| self.constArrayLiteral(al.elements),
else => null,
} else null;
const gid = self.module.addGlobal(.{
@@ -467,6 +468,31 @@ pub const Lowering = struct {
}
}
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
/// Returns null if any element is not a compile-time constant.
fn constArrayLiteral(self: *Lowering, elements: []const *const Node) ?inst_mod.ConstantValue {
const vals = self.alloc.alloc(inst_mod.ConstantValue, elements.len) catch return null;
for (elements, 0..) |elem, i| {
vals[i] = switch (elem.data) {
.int_literal => |il| .{ .int = il.value },
.bool_literal => |bl| .{ .boolean = bl.value },
.float_literal => |fl| .{ .float = fl.value },
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
.unary_op => |uo| switch (uo.op) {
.negate => switch (uo.operand.data) {
.int_literal => |il| .{ .int = -il.value },
.float_literal => |fl| .{ .float = -fl.value },
else => return null,
},
else => return null,
},
.array_literal => |al| self.constArrayLiteral(al.elements) orelse return null,
else => return null,
};
}
return .{ .aggregate = vals };
}
/// Pass 2: Lower main function body and comptime side-effects.
fn lowerMainAndComptime(self: *Lowering, decls: []const *const Node) void {
for (decls) |decl| {
@@ -514,19 +540,23 @@ pub const Lowering = struct {
}) catch unreachable;
}
const cc: Function.CallingConvention = if (fd.call_conv == .c) .c else .default;
// For #foreign with C name override, declare under C name and map sx name → C name
if (fd.body.data == .foreign_expr) {
const fe = fd.body.data.foreign_expr;
if (fe.c_name) |c_name| {
const c_name_id = self.module.types.internString(c_name);
_ = self.builder.declareExtern(c_name_id, params.items, ret_ty);
const fid = self.builder.declareExtern(c_name_id, params.items, ret_ty);
self.module.getFunctionMut(fid).call_conv = cc;
self.foreign_name_map.put(name, c_name) catch {};
return;
}
}
const name_id = self.module.types.internString(name);
_ = self.builder.declareExtern(name_id, params.items, ret_ty);
const fid = self.builder.declareExtern(name_id, params.items, ret_ty);
self.module.getFunctionMut(fid).call_conv = cc;
}
/// Lazily lower a function body on demand. Called when lowerCall can't find
@@ -607,6 +637,7 @@ pub const Lowering = struct {
}
func.is_extern = false; // promote from extern stub to real function
func.linkage = if (std.mem.eql(u8, name, "main")) .external else .internal;
if (fd.call_conv == .c) func.call_conv = .c;
// Set inst_counter to param count (params occupy refs 0..N-1)
std.debug.assert(func.params.len == fd.params.len); // AST and IR param counts must match
self.builder.inst_counter = @intCast(func.params.len);
@@ -717,6 +748,11 @@ pub const Lowering = struct {
self.builder.currentFunc().linkage = .external;
}
// Set calling convention
if (fd.call_conv == .c) {
self.builder.currentFunc().call_conv = .c;
}
// Create entry block
const entry_name = self.module.types.internString("entry");
const entry = self.builder.appendBlock(entry_name, &.{});
@@ -1070,12 +1106,14 @@ pub const Lowering = struct {
// Set target_type from LHS for RHS lowering (enum literals, struct literals, etc.)
const old_target = self.target_type;
if (asgn.target.data == .identifier) {
var found_local = false;
if (self.scope) |scope| {
if (scope.lookup(asgn.target.data.identifier.name)) |binding| {
self.target_type = binding.ty;
found_local = true;
}
}
if (self.target_type == null) {
if (!found_local) {
if (self.global_names.get(asgn.target.data.identifier.name)) |gi| {
self.target_type = gi.ty;
}
@@ -4387,6 +4425,9 @@ pub const Lowering = struct {
};
const name_id = self.module.types.internString(name);
const func_id = self.builder.beginFunction(name_id, params.items, ret_ty);
if (lam.call_conv == .c) {
self.module.getFunctionMut(func_id).call_conv = .c;
}
// Create entry block
const entry_name = self.module.types.internString("entry");
@@ -6436,6 +6477,58 @@ pub const Lowering = struct {
/// Resolve parameter types for a call expression (for target_type context).
/// Returns empty slice if the function can't be resolved.
fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call) []const TypeId {
// Method calls: obj.method(args) — resolve param types from the method signature,
// skipping the first param (self) since it's prepended later.
if (c.callee.data == .field_access) {
const fa = c.callee.data.field_access;
const obj_ty = self.inferExprType(fa.object);
if (self.getStructTypeName(obj_ty)) |sname| {
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch return &.{};
// Try already-lowered functions first
if (self.resolveFuncByName(qualified)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
if (func.params.len > 0) {
// Skip self param — caller args don't include self
var types_list = std.ArrayList(TypeId).empty;
for (func.params[1..]) |p| {
types_list.append(self.alloc, p.ty) catch unreachable;
}
return types_list.items;
}
}
// Try AST map (not yet lowered)
if (self.fn_ast_map.get(qualified)) |fd| {
if (fd.params.len > 0) {
var types_list = std.ArrayList(TypeId).empty;
for (fd.params[1..]) |p| {
types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable;
}
return types_list.items;
}
}
// Try generic struct template method: List__Container.append → List.append
// with type bindings from the struct instantiation
if (self.struct_instance_template.get(sname)) |tmpl_name| {
const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch return &.{};
if (self.fn_ast_map.get(tmpl_qualified)) |fd| {
if (fd.params.len > 0) {
// Temporarily set type_bindings so resolveParamType can substitute T → concrete type
const saved_bindings = self.type_bindings;
if (self.struct_instance_bindings.getPtr(sname)) |bindings| {
self.type_bindings = bindings.*;
}
var types_list = std.ArrayList(TypeId).empty;
for (fd.params[1..]) |p| {
types_list.append(self.alloc, self.resolveParamType(&p)) catch unreachable;
}
self.type_bindings = saved_bindings;
return types_list.items;
}
}
}
}
return &.{};
}
if (c.callee.data != .identifier) return &.{};
const bare_name = c.callee.data.identifier.name;
const name = blk: {

View File

@@ -1478,6 +1478,7 @@ pub const Server = struct {
.kw_protocol,
.kw_impl,
.kw_inline,
.kw_callconv,
.hash_run,
.hash_import,
.hash_insert,

View File

@@ -1220,6 +1220,24 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr();
}
// Optional calling convention: callconv(.c)
var call_conv: ast.CallingConvention = .default;
if (self.current.tag == .kw_callconv) {
self.advance();
try self.expect(.l_paren);
try self.expect(.dot);
if (self.current.tag != .identifier)
return self.fail("expected calling convention name after '.'");
const cc_name = self.tokenSlice(self.current);
if (std.mem.eql(u8, cc_name, "c")) {
call_conv = .c;
} else {
return self.fail("unknown calling convention");
}
self.advance();
try self.expect(.r_paren);
}
// Body: block `{ ... }`, arrow `=> expr;`, #builtin, #compiler, or #foreign marker
var is_arrow = false;
const body = if (self.current.tag == .hash_builtin) blk: {
@@ -1273,6 +1291,7 @@ pub const Parser = struct {
.body = body,
.type_params = type_params,
.is_arrow = is_arrow,
.call_conv = call_conv,
} });
}
@@ -2333,6 +2352,24 @@ pub const Parser = struct {
return_type = try self.parseTypeExpr();
}
// Optional calling convention: callconv(.c)
var call_conv: ast.CallingConvention = .default;
if (self.current.tag == .kw_callconv) {
self.advance();
try self.expect(.l_paren);
try self.expect(.dot);
if (self.current.tag != .identifier)
return self.fail("expected calling convention name after '.'");
const cc_name = self.tokenSlice(self.current);
if (std.mem.eql(u8, cc_name, "c")) {
call_conv = .c;
} else {
return self.fail("unknown calling convention");
}
self.advance();
try self.expect(.r_paren);
}
// Two body forms:
// (params) => expr — expression lambda
// (params) { stmts } — block-body lambda
@@ -2348,6 +2385,7 @@ pub const Parser = struct {
.return_type = return_type,
.body = body,
.type_params = type_params,
.call_conv = call_conv,
} });
}
@@ -2365,7 +2403,7 @@ pub const Parser = struct {
fn isFunctionDef(self: *Parser) bool {
const tag = self.peekPastParens() orelse return false;
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow;
return tag == .l_brace or tag == .arrow or tag == .hash_builtin or tag == .hash_compiler or tag == .hash_foreign or tag == .fat_arrow or tag == .kw_callconv;
}
fn isAssignOp(self: *const Parser) bool {

View File

@@ -37,6 +37,7 @@ pub const Tag = enum {
kw_impl, // impl
kw_Self, // Self (in protocol declarations)
kw_inline, // inline (compile-time if/for/while)
kw_callconv, // callconv (calling convention annotation)
// Symbols
colon, // :
@@ -227,6 +228,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "impl", .kw_impl },
.{ "Self", .kw_Self },
.{ "inline", .kw_inline },
.{ "callconv", .kw_callconv },
});
pub fn getKeyword(bytes: []const u8) ?Tag {