fixes
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1478,6 +1478,7 @@ pub const Server = struct {
|
||||
.kw_protocol,
|
||||
.kw_impl,
|
||||
.kw_inline,
|
||||
.kw_callconv,
|
||||
.hash_run,
|
||||
.hash_import,
|
||||
.hash_insert,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user