sdl phase 1

This commit is contained in:
agra
2026-02-12 12:27:35 +02:00
parent cfac6791cb
commit 1087bd1977
10 changed files with 820 additions and 10 deletions

View File

@@ -177,6 +177,10 @@ pub const CodeGen = struct {
foreign_libraries: std.ArrayList([]const u8),
// Set of foreign function names (for ABI lowering at call sites)
foreign_fns: std.StringHashMap(void),
// Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime)
global_mutable_vars: std.StringHashMap(NamedValue),
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
function_return_types: std.StringHashMap(Type),
// Target configuration (triple, cpu, opt level, lib paths, linker)
target_config: TargetConfig = .{},
@@ -293,6 +297,8 @@ pub const CodeGen = struct {
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
.foreign_libraries = std.ArrayList([]const u8).empty,
.foreign_fns = std.StringHashMap(void).init(allocator),
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
.function_return_types = std.StringHashMap(Type).init(allocator),
.target_config = target_config,
};
}
@@ -366,7 +372,7 @@ pub const CodeGen = struct {
const elem_ty = Type.fromName(info.element_name) orelse unreachable;
return c.LLVMVectorType(self.typeToLLVM(elem_ty), info.length);
},
.pointer_type, .many_pointer_type => c.LLVMPointerTypeInContext(self.context, 0),
.pointer_type, .many_pointer_type, .function_type => c.LLVMPointerTypeInContext(self.context, 0),
.any_type => self.getAnyStructType(),
.meta_type => c.LLVMPointerTypeInContext(self.context, 0),
};
@@ -741,6 +747,9 @@ pub const CodeGen = struct {
.namespace_decl => |ns| {
try self.registerNamespace(ns);
},
.var_decl => |vd| {
try self.registerGlobalVar(vd);
},
else => {},
}
}
@@ -937,6 +946,21 @@ pub const CodeGen = struct {
const elem_name = elem_type.displayName(self.allocator) catch unreachable;
return .{ .many_pointer_type = .{ .element_name = elem_name } };
}
// Function pointer type: (ParamTypes) -> ReturnType
if (tn.data == .function_type_expr) {
const fte = tn.data.function_type_expr;
var param_types = std.ArrayList(Type).empty;
for (fte.param_types) |pt| {
param_types.append(self.allocator, self.resolveType(pt)) catch return .void_type;
}
const ret_ty = if (fte.return_type) |rt| self.resolveType(rt) else Type.void_type;
const ret_ptr = self.allocator.create(Type) catch return .void_type;
ret_ptr.* = ret_ty;
return .{ .function_type = .{
.param_types = param_types.toOwnedSlice(self.allocator) catch return .void_type,
.return_type = ret_ptr,
} };
}
// Parameterized type: Vector(N, T) or generic struct instantiation
if (tn.data == .parameterized_type_expr) {
const pte = tn.data.parameterized_type_expr;
@@ -1482,6 +1506,9 @@ pub const CodeGen = struct {
}
}
try self.fn_param_types.put(llvm_name, try param_types.toOwnedSlice(self.allocator));
// Track declared return type (preserves signedness lost by LLVM round-trip)
const ret_ty = if (fd.return_type) |rt| self.resolveType(rt) else Type.void_type;
try self.function_return_types.put(llvm_name, ret_ty);
// Track variadic function info for call site packing
for (fd.params, 0..) |param, i| {
if (param.is_variadic) {
@@ -1553,6 +1580,9 @@ pub const CodeGen = struct {
.library_decl => |ld| {
try self.foreign_libraries.append(self.allocator, ld.lib_name);
},
.var_decl => |vd| {
try self.registerGlobalVar(vd);
},
else => {},
}
}
@@ -1703,6 +1733,22 @@ pub const CodeGen = struct {
});
}
fn registerGlobalVar(self: *CodeGen, vd: ast.VarDecl) !void {
const ta = vd.type_annotation orelse return;
const sx_ty = self.resolveType(ta);
if (sx_ty == .void_type) return;
const llvm_ty = self.typeToLLVM(sx_ty);
const name_z = try self.allocator.dupeZ(u8, vd.name);
const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr);
// Initialize with undef (will be set at runtime, e.g. by load_gl)
c.LLVMSetInitializer(global, c.LLVMGetUndef(llvm_ty));
// NOT constant — this is a mutable global
c.LLVMSetGlobalConstant(global, 0);
try self.global_mutable_vars.put(vd.name, .{ .ptr = global, .ty = sx_ty });
}
fn bindParam(self: *CodeGen, function: c.LLVMValueRef, name: []const u8, sx_ty: Type, param_idx: u32) !void {
const llvm_ty = self.typeToLLVM(sx_ty);
const param_name_z = try self.allocator.dupeZ(u8, name);
@@ -2175,6 +2221,34 @@ pub const CodeGen = struct {
return null;
}
// Function pointer typed variable
if (sx_ty.isFunctionType()) {
const llvm_ty = c.LLVMPointerTypeInContext(self.context, 0);
const name_z = try self.allocator.dupeZ(u8, vd.name);
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
if (vd.value == null) {
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_ty), alloca);
} else if (vd.value.?.data == .undef_literal) {
_ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(llvm_ty), alloca);
} else if (vd.value.?.data == .unary_op and vd.value.?.data.unary_op.op == .xx) {
// xx cast: e.g. xx SDL_GL_GetProcAddress("glClear")
const inner = vd.value.?.data.unary_op.operand;
const val = try self.genExpr(inner);
const src_ty = self.inferType(inner);
const converted = self.convertValue(val, src_ty, sx_ty);
_ = c.LLVMBuildStore(self.builder, converted, alloca);
} else {
// Direct assignment: identifier (function name) or other expression
const val = try self.genExpr(vd.value.?);
_ = c.LLVMBuildStore(self.builder, val, alloca);
}
try self.saveShadowed(vd.name);
try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty });
return null;
}
// Guard: void type cannot be allocated (would crash LLVM)
if (sx_ty == .void_type) {
return self.emitErrorFmt("cannot declare variable '{s}' with void type", .{vd.name});
@@ -2261,6 +2335,26 @@ pub const CodeGen = struct {
return null;
}
// Function pointer typed constant
if (sx_ty.isFunctionType()) {
const llvm_ty = c.LLVMPointerTypeInContext(self.context, 0);
const name_z = try self.allocator.dupeZ(u8, cd.name);
const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr);
if (cd.value.data == .unary_op and cd.value.data.unary_op.op == .xx) {
const inner = cd.value.data.unary_op.operand;
const val = try self.genExpr(inner);
const src_inner_ty = self.inferType(inner);
const converted = self.convertValue(val, src_inner_ty, sx_ty);
_ = c.LLVMBuildStore(self.builder, converted, alloca);
} else {
const val = try self.genExpr(cd.value);
_ = c.LLVMBuildStore(self.builder, val, alloca);
}
try self.saveShadowed(cd.name);
try self.named_values.put(cd.name, .{ .ptr = alloca, .ty = sx_ty });
return null;
}
const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null;
const init_val = if (cd.value.data == .enum_literal and enum_name != null)
self.genEnumLiteral(cd.value.data.enum_literal.name, enum_name.?)
@@ -2313,7 +2407,8 @@ 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 {
const entry = self.named_values.get(name) orelse
self.global_mutable_vars.get(name) orelse {
return self.emitErrorFmt("undefined variable '{s}'", .{name});
};
@@ -2339,6 +2434,21 @@ pub const CodeGen = struct {
return self.emitErrorFmt("cannot assign non-type value to Type variable '{s}'", .{name});
}
// Function pointer reassignment
if (entry.ty.isFunctionType() and asgn.op == .assign) {
if (asgn.value.data == .unary_op and asgn.value.data.unary_op.op == .xx) {
const inner = asgn.value.data.unary_op.operand;
const val = try self.genExpr(inner);
const src_ty = self.inferType(inner);
const converted = self.convertValue(val, src_ty, entry.ty);
_ = c.LLVMBuildStore(self.builder, converted, entry.ptr);
} else {
const val = try self.genExpr(asgn.value);
_ = c.LLVMBuildStore(self.builder, val, entry.ptr);
}
return null;
}
// Union reassignment: s = .circle(3.14) or s = .none
if (entry.ty.isUnion() and asgn.op == .assign) {
const new_alloca = try self.genExprAsType(asgn.value, entry.ty);
@@ -2556,6 +2666,25 @@ pub const CodeGen = struct {
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
{
const name_z = try self.allocator.dupeZ(u8, ident.name);
var fn_val = c.LLVMGetNamedFunction(self.module, name_z.ptr);
if (fn_val == null) {
// Try qualified name with current namespace
if (self.current_namespace) |ns| {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, ident.name });
const q_z = try self.allocator.dupeZ(u8, qualified);
fn_val = c.LLVMGetNamedFunction(self.module, q_z.ptr);
}
}
if (fn_val != null) return fn_val.?;
}
return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name});
},
.binary_op => |binop| {
@@ -3115,6 +3244,11 @@ pub const CodeGen = struct {
return self.convertValue(val, src_ty, target_ty);
}
// Function pointer target: bypass narrowing check, just produce the pointer value
if (target_ty.isFunctionType()) {
return try self.genExpr(node);
}
// String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping)
if (node.data == .string_literal and target_ty.isPointer()) {
const unescaped = try unescapeString(self.allocator, node.data.string_literal.raw);
@@ -3431,6 +3565,19 @@ pub const CodeGen = struct {
return val;
}
// Int → pointer/function_type: IntToPtr (for xx cast from integer to pointer)
if (src_ty.isInt() and (target_ty.isPointer() or target_ty.isManyPointer() or target_ty.isFunctionType())) {
return c.LLVMBuildIntToPtr(self.builder, val, c.LLVMPointerTypeInContext(self.context, 0), "inttoptr");
}
// Pointer → function_type or function_type → pointer: both are opaque pointers, no-op
if ((src_ty.isPointer() or src_ty.isManyPointer()) and target_ty.isFunctionType()) {
return val;
}
if (src_ty.isFunctionType() and (target_ty.isPointer() or target_ty.isManyPointer())) {
return val;
}
return val;
}
@@ -3482,6 +3629,50 @@ pub const CodeGen = struct {
);
}
fn genSin(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef {
if (call_node.args.len != 1) return self.emitError("sin expects exactly 1 argument");
const arg_val = try self.genExpr(call_node.args[0]);
const arg_ty = self.inferType(call_node.args[0]);
const intrinsic_name: [*c]const u8 = if (std.meta.eql(arg_ty, Type.f64)) "llvm.sin.f64" else "llvm.sin.f32";
const llvm_float_ty = if (std.meta.eql(arg_ty, Type.f64))
c.LLVMDoubleTypeInContext(self.context)
else
c.LLVMFloatTypeInContext(self.context);
var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name);
if (intrinsic_fn == null) {
var param_types = [_]c.LLVMTypeRef{llvm_float_ty};
const fn_type = c.LLVMFunctionType(llvm_float_ty, &param_types, 1, 0);
intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type);
}
var args = [_]c.LLVMValueRef{arg_val};
return c.LLVMBuildCall2(self.builder, c.LLVMGlobalGetValueType(intrinsic_fn.?), intrinsic_fn.?, &args, 1, "sin");
}
fn genCos(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef {
if (call_node.args.len != 1) return self.emitError("cos expects exactly 1 argument");
const arg_val = try self.genExpr(call_node.args[0]);
const arg_ty = self.inferType(call_node.args[0]);
const intrinsic_name: [*c]const u8 = if (std.meta.eql(arg_ty, Type.f64)) "llvm.cos.f64" else "llvm.cos.f32";
const llvm_float_ty = if (std.meta.eql(arg_ty, Type.f64))
c.LLVMDoubleTypeInContext(self.context)
else
c.LLVMFloatTypeInContext(self.context);
var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name);
if (intrinsic_fn == null) {
var param_types = [_]c.LLVMTypeRef{llvm_float_ty};
const fn_type = c.LLVMFunctionType(llvm_float_ty, &param_types, 1, 0);
intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type);
}
var args = [_]c.LLVMValueRef{arg_val};
return c.LLVMBuildCall2(self.builder, c.LLVMGlobalGetValueType(intrinsic_fn.?), intrinsic_fn.?, &args, 1, "cos");
}
fn genSizeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef {
if (call_node.args.len != 1) return self.emitError("size_of expects exactly 1 argument");
const ty = self.resolveType(call_node.args[0]);
@@ -4318,6 +4509,12 @@ pub const CodeGen = struct {
if (std.mem.eql(u8, callee_name, "sqrt")) {
return self.genSqrt(call_node);
}
if (std.mem.eql(u8, callee_name, "sin")) {
return self.genSin(call_node);
}
if (std.mem.eql(u8, callee_name, "cos")) {
return self.genCos(call_node);
}
if (std.mem.eql(u8, callee_name, "cast")) {
return self.genCast(call_node);
}
@@ -4349,7 +4546,17 @@ pub const CodeGen = struct {
callee_fn = c.LLVMGetNamedFunction(self.module, qualified_z.ptr);
}
}
if (callee_fn == null) return self.emitErrorFmt("undefined function '{s}'", .{callee_name});
// 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);
}
}
return self.emitErrorFmt("undefined function '{s}'", .{callee_name});
}
// Get function type (opaque pointers: use LLVMGlobalGetValueType)
const fn_type = c.LLVMGlobalGetValueType(callee_fn.?);
@@ -4511,6 +4718,48 @@ pub const CodeGen = struct {
);
}
fn genIndirectCall(self: *CodeGen, entry: NamedValue, call_node: ast.Call) !c.LLVMValueRef {
const fti = entry.ty.function_type;
// Load the function pointer from the alloca
const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0);
const fn_ptr = c.LLVMBuildLoad2(self.builder, ptr_ty, entry.ptr, "fn_ptr");
// Build LLVM function type from FunctionTypeInfo
var param_llvm_types: [64]c.LLVMTypeRef = undefined;
for (fti.param_types, 0..) |pt, i| {
param_llvm_types[i] = self.typeToLLVM(pt);
}
const ret_llvm = self.typeToLLVM(fti.return_type.*);
const fn_type = c.LLVMFunctionType(
ret_llvm,
if (fti.param_types.len > 0) &param_llvm_types else null,
@intCast(fti.param_types.len),
0,
);
// Generate arguments with type conversion
var arg_vals = std.ArrayList(c.LLVMValueRef).empty;
for (call_node.args, 0..) |arg, i| {
if (i < fti.param_types.len) {
try arg_vals.append(self.allocator, try self.genExprAsType(arg, fti.param_types[i]));
} else {
try arg_vals.append(self.allocator, try self.genExpr(arg));
}
}
const args_slice = try arg_vals.toOwnedSlice(self.allocator);
const call_name: [*c]const u8 = if (ret_llvm == c.LLVMVoidTypeInContext(self.context)) "" else "calltmp";
return c.LLVMBuildCall2(
self.builder,
fn_type,
fn_ptr,
if (args_slice.len > 0) args_slice.ptr else null,
@intCast(args_slice.len),
call_name,
);
}
fn genGenericCall(self: *CodeGen, qualified_name: []const u8, template: GenericTemplate, call_node: ast.Call) !c.LLVMValueRef {
const fd = template.fd;
@@ -5690,6 +5939,8 @@ pub const CodeGen = struct {
const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name;
if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args);
if (std.mem.eql(u8, base, "sqrt")) return self.genSqrt(call_node);
if (std.mem.eql(u8, base, "sin")) return self.genSin(call_node);
if (std.mem.eql(u8, base, "cos")) return self.genCos(call_node);
if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node);
if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node);
if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args);
@@ -5871,6 +6122,7 @@ pub const CodeGen = struct {
.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;
return Type.s(64);
},
.if_expr => |ie| {
@@ -5915,8 +6167,11 @@ pub const CodeGen = struct {
}
const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64);
const base_name = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name;
// Built-in: sqrt returns same type as argument
if (std.mem.eql(u8, base_name, "sqrt")) {
// Built-in: sqrt/sin/cos returns same type as argument
if (std.mem.eql(u8, base_name, "sqrt") or
std.mem.eql(u8, base_name, "sin") or
std.mem.eql(u8, base_name, "cos"))
{
if (call_node.args.len > 0) return self.inferType(call_node.args[0]);
return .f32;
}
@@ -5985,13 +6240,19 @@ pub const CodeGen = struct {
}
return Type.s(64);
}
// Check non-generic LLVM functions
// Check declared return types (preserves signedness)
if (self.function_return_types.get(callee_name)) |ret_ty| return ret_ty;
if (self.current_namespace) |ns| {
const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64);
if (self.function_return_types.get(qualified)) |ret_ty| return ret_ty;
}
// Fallback: check non-generic LLVM functions
const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64);
var callee_fn_opt = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr);
// Intra-namespace fallback
if (callee_fn_opt == null) {
if (self.current_namespace) |ns| {
const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64);
if (self.current_namespace) |ns2| {
const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns2, callee_name }) catch return Type.s(64);
const qz = self.allocator.dupeZ(u8, q) catch return Type.s(64);
callee_fn_opt = c.LLVMGetNamedFunction(self.module, qz.ptr);
}
@@ -6001,6 +6262,16 @@ pub const CodeGen = struct {
const ret_llvm = c.LLVMGetReturnType(fn_type);
return self.llvmTypeToSxType(ret_llvm);
}
// 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.*;
}
}
}
return Type.s(64);
},
.unary_op => |unop| {