ir done'ish
This commit is contained in:
@@ -1782,6 +1782,24 @@ END;
|
|||||||
print("opt param 7: {}\n", opt_process(7)); // opt param 7: 7
|
print("opt param 7: {}\n", opt_process(7)); // opt param 7: 7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assignment to optional variable (f32 → ?f32)
|
||||||
|
{
|
||||||
|
iw: ?f32 = null;
|
||||||
|
w: f32 = 42.5;
|
||||||
|
iw = w;
|
||||||
|
print("opt reassign: {}\n", iw ?? 0.0); // opt reassign: 42.5
|
||||||
|
|
||||||
|
// Assignment of computed value to optional
|
||||||
|
iw2: ?f32 = null;
|
||||||
|
a: ?f32 = 10.0;
|
||||||
|
if v := a { iw2 = v + 5.0; }
|
||||||
|
print("opt compute assign: {}\n", iw2 ?? 0.0); // opt compute assign: 15.0
|
||||||
|
|
||||||
|
// Re-assign optional back to null
|
||||||
|
iw2 = null;
|
||||||
|
print("opt re-null: {}\n", iw2 ?? 99.0); // opt re-null: 99.0
|
||||||
|
}
|
||||||
|
|
||||||
// Generic function with ?T return
|
// Generic function with ?T return
|
||||||
{
|
{
|
||||||
first_pos :: ($T: Type, a: T, b: T) -> ?T {
|
first_pos :: ($T: Type, a: T, b: T) -> ?T {
|
||||||
|
|||||||
11905
src/codegen.zig
11905
src/codegen.zig
File diff suppressed because it is too large
Load Diff
2752
src/comptime.zig
2752
src/comptime.zig
File diff suppressed because it is too large
Load Diff
22
src/core.zig
22
src/core.zig
@@ -3,13 +3,13 @@ const ast = @import("ast.zig");
|
|||||||
const parser = @import("parser.zig");
|
const parser = @import("parser.zig");
|
||||||
const imports = @import("imports.zig");
|
const imports = @import("imports.zig");
|
||||||
const sema = @import("sema.zig");
|
const sema = @import("sema.zig");
|
||||||
const codegen = @import("codegen.zig");
|
|
||||||
const errors = @import("errors.zig");
|
const errors = @import("errors.zig");
|
||||||
const c_import = @import("c_import.zig");
|
const c_import = @import("c_import.zig");
|
||||||
const ir = @import("ir/ir.zig");
|
const ir = @import("ir/ir.zig");
|
||||||
|
const target_mod = @import("target.zig");
|
||||||
const Node = ast.Node;
|
const Node = ast.Node;
|
||||||
|
|
||||||
pub const TargetConfig = codegen.TargetConfig;
|
pub const TargetConfig = target_mod.TargetConfig;
|
||||||
|
|
||||||
pub const Compilation = struct {
|
pub const Compilation = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
@@ -24,7 +24,6 @@ pub const Compilation = struct {
|
|||||||
resolved_root: ?*Node = null,
|
resolved_root: ?*Node = null,
|
||||||
import_sources: std.StringHashMap([:0]const u8),
|
import_sources: std.StringHashMap([:0]const u8),
|
||||||
sema_result: ?sema.SemaResult = null,
|
sema_result: ?sema.SemaResult = null,
|
||||||
cg: ?codegen.CodeGen = null,
|
|
||||||
ir_emitter: ?ir.LLVMEmitter = null,
|
ir_emitter: ?ir.LLVMEmitter = null,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8, source: [:0]const u8, target_config: TargetConfig) Compilation {
|
pub fn init(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8, source: [:0]const u8, target_config: TargetConfig) Compilation {
|
||||||
@@ -41,7 +40,6 @@ pub const Compilation = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Compilation) void {
|
pub fn deinit(self: *Compilation) void {
|
||||||
if (self.ir_emitter) |*e| e.deinit();
|
if (self.ir_emitter) |*e| e.deinit();
|
||||||
if (self.cg) |*cg| cg.deinit();
|
|
||||||
self.diagnostics.deinit();
|
self.diagnostics.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,21 +93,8 @@ pub const Compilation = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generateCode(self: *Compilation) !void {
|
|
||||||
const root = self.resolved_root orelse self.root orelse return error.CompileError;
|
|
||||||
var cg = codegen.CodeGen.init(self.allocator, "sx_module", self.target_config);
|
|
||||||
cg.diagnostics = &self.diagnostics;
|
|
||||||
cg.import_sources = &self.import_sources;
|
|
||||||
if (self.sema_result) |*sr| {
|
|
||||||
cg.sema_result = sr;
|
|
||||||
}
|
|
||||||
cg.generate(root) catch return error.CompileError;
|
|
||||||
|
|
||||||
self.cg = cg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate code via the IR pipeline: lower AST → IR → LLVM.
|
/// Generate code via the IR pipeline: lower AST → IR → LLVM.
|
||||||
pub fn generateCodeViaIR(self: *Compilation) !void {
|
pub fn generateCode(self: *Compilation) !void {
|
||||||
// Heap-allocate the IR module so its address is stable during emit
|
// Heap-allocate the IR module so its address is stable during emit
|
||||||
const ir_mod_ptr = try self.allocator.create(ir.Module);
|
const ir_mod_ptr = try self.allocator.create(ir.Module);
|
||||||
ir_mod_ptr.* = self.lowerToIR();
|
ir_mod_ptr.* = self.lowerToIR();
|
||||||
@@ -122,7 +107,6 @@ pub const Compilation = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect C import source info from the resolved AST.
|
/// Collect C import source info from the resolved AST.
|
||||||
/// Called after generateCode() to compile C sources natively (not merged into LLVM module).
|
|
||||||
pub fn collectCImportSources(self: *Compilation) ![]c_import.CImportInfo {
|
pub fn collectCImportSources(self: *Compilation) ![]c_import.CImportInfo {
|
||||||
const root = self.resolved_root orelse self.root orelse return &.{};
|
const root = self.resolved_root orelse self.root orelse return &.{};
|
||||||
return c_import.collectCImportSources(self.allocator, root);
|
return c_import.collectCImportSources(self.allocator, root);
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ test "emit: struct_gep (pointer to field)" {
|
|||||||
b.switchToBlock(entry);
|
b.switchToBlock(entry);
|
||||||
|
|
||||||
const p_ptr = b.alloca(point_ty);
|
const p_ptr = b.alloca(point_ty);
|
||||||
const y_ptr = b.structGep(p_ptr, 1, ptr_s64);
|
const y_ptr = b.structGepTyped(p_ptr, 1, ptr_s64, point_ty);
|
||||||
const c42 = b.constInt(42, .s64);
|
const c42 = b.constInt(42, .s64);
|
||||||
b.store(y_ptr, c42);
|
b.store(y_ptr, c42);
|
||||||
const loaded = b.load(y_ptr, .s64);
|
const loaded = b.load(y_ptr, .s64);
|
||||||
@@ -482,7 +482,7 @@ test "emit: enum_init and enum_tag (plain enum)" {
|
|||||||
b.switchToBlock(entry);
|
b.switchToBlock(entry);
|
||||||
|
|
||||||
const green = b.enumInit(1, Ref.none, color_ty); // Green = tag 1
|
const green = b.enumInit(1, Ref.none, color_ty); // Green = tag 1
|
||||||
const tag = b.enumTag(green);
|
const tag = b.enumTag(green, .s32);
|
||||||
// Widen tag from s32 to s64 for the return
|
// Widen tag from s32 to s64 for the return
|
||||||
const wide = b.widen(tag, .s32, .s64);
|
const wide = b.widen(tag, .s32, .s64);
|
||||||
b.ret(wide, .s64);
|
b.ret(wide, .s64);
|
||||||
@@ -511,10 +511,10 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" {
|
|||||||
};
|
};
|
||||||
const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable;
|
const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable;
|
||||||
defer alloc.free(owned_ufields);
|
defer alloc.free(owned_ufields);
|
||||||
const shape_ty = module.types.intern(.{ .@"union" = .{
|
const shape_ty = module.types.intern(.{ .tagged_union = .{
|
||||||
.name = str(&module, "Shape"),
|
.name = str(&module, "Shape"),
|
||||||
.fields = owned_ufields,
|
.fields = owned_ufields,
|
||||||
.tag_type = null,
|
.tag_type = .s64,
|
||||||
} });
|
} });
|
||||||
|
|
||||||
var b = Builder.init(&module);
|
var b = Builder.init(&module);
|
||||||
@@ -558,7 +558,6 @@ test "emit: union_get (reinterpret union field)" {
|
|||||||
const data_ty = module.types.intern(.{ .@"union" = .{
|
const data_ty = module.types.intern(.{ .@"union" = .{
|
||||||
.name = str(&module, "Data"),
|
.name = str(&module, "Data"),
|
||||||
.fields = owned_ufields,
|
.fields = owned_ufields,
|
||||||
.tag_type = null,
|
|
||||||
} });
|
} });
|
||||||
|
|
||||||
var b = Builder.init(&module);
|
var b = Builder.init(&module);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const llvm = @import("../llvm_api.zig");
|
const llvm = @import("../llvm_api.zig");
|
||||||
const c = llvm.c;
|
const c = llvm.c;
|
||||||
const codegen = @import("../codegen.zig");
|
const target_mod = @import("../target.zig");
|
||||||
const TargetConfig = codegen.TargetConfig;
|
const TargetConfig = target_mod.TargetConfig;
|
||||||
const ir_types = @import("types.zig");
|
const ir_types = @import("types.zig");
|
||||||
const TypeId = ir_types.TypeId;
|
const TypeId = ir_types.TypeId;
|
||||||
const TypeInfo = ir_types.TypeInfo;
|
const TypeInfo = ir_types.TypeInfo;
|
||||||
@@ -188,6 +188,29 @@ pub const LLVMEmitter = struct {
|
|||||||
if (func.is_extern or func.blocks.items.len == 0) continue;
|
if (func.is_extern or func.blocks.items.len == 0) continue;
|
||||||
self.emitFunction(&func, @intCast(i));
|
self.emitFunction(&func, @intCast(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 3: Verify typeSizeBytes matches LLVM's ABI sizes
|
||||||
|
self.verifySizes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare IR typeSizeBytes against LLVMABISizeOfType for all user-defined types.
|
||||||
|
fn verifySizes(self: *LLVMEmitter) void {
|
||||||
|
const dl = c.LLVMGetModuleDataLayout(self.llvm_module);
|
||||||
|
if (dl == null) return;
|
||||||
|
const type_count = self.ir_mod.types.infos.items.len;
|
||||||
|
for (TypeId.first_user..type_count) |idx| {
|
||||||
|
const ty = TypeId.fromIndex(@intCast(idx));
|
||||||
|
const info = self.ir_mod.types.get(ty);
|
||||||
|
// Only verify aggregate types where sizing is non-trivial
|
||||||
|
switch (info) {
|
||||||
|
.@"struct", .@"union", .tagged_union, .tuple => {},
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
const llvm_ty = self.toLLVMType(ty);
|
||||||
|
const llvm_size = c.LLVMABISizeOfType(dl, llvm_ty);
|
||||||
|
const ir_size = self.ir_mod.types.typeSizeBytes(ty);
|
||||||
|
std.debug.assert(llvm_size == ir_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run comptime side-effect functions (e.g., `#run main();` at top level).
|
/// Run comptime side-effect functions (e.g., `#run main();` at top level).
|
||||||
@@ -241,6 +264,7 @@ pub const LLVMEmitter = struct {
|
|||||||
.int => |v| c.LLVMConstInt(llvm_ty, @bitCast(v), 1),
|
.int => |v| c.LLVMConstInt(llvm_ty, @bitCast(v), 1),
|
||||||
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
||||||
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
||||||
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
||||||
else => c.LLVMConstNull(llvm_ty),
|
else => c.LLVMConstNull(llvm_ty),
|
||||||
};
|
};
|
||||||
c.LLVMSetInitializer(llvm_global, init_val);
|
c.LLVMSetInitializer(llvm_global, init_val);
|
||||||
@@ -269,14 +293,16 @@ pub const LLVMEmitter = struct {
|
|||||||
const is_main = std.mem.eql(u8, name, "main");
|
const is_main = std.mem.eql(u8, name, "main");
|
||||||
|
|
||||||
// main always returns i32 at the LLVM level (JIT expects it)
|
// main always returns i32 at the LLVM level (JIT expects it)
|
||||||
const ret_ty = if (is_main) self.cached_i32 else self.toLLVMType(func.ret);
|
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;
|
||||||
|
|
||||||
// Build parameter types
|
// Build parameter types — apply C ABI coercion for foreign (extern) functions
|
||||||
const param_count: c_uint = @intCast(func.params.len);
|
const param_count: c_uint = @intCast(func.params.len);
|
||||||
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
|
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
|
||||||
defer self.alloc.free(param_types);
|
defer self.alloc.free(param_types);
|
||||||
for (func.params, 0..) |param, j| {
|
for (func.params, 0..) |param, j| {
|
||||||
param_types[j] = self.toLLVMType(param.ty);
|
const llvm_ty = self.toLLVMType(param.ty);
|
||||||
|
param_types[j] = if (func.is_extern) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
|
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
|
||||||
@@ -356,7 +382,8 @@ pub const LLVMEmitter = struct {
|
|||||||
// (blocks may not be in emission order due to nested control flow)
|
// (blocks may not be in emission order due to nested control flow)
|
||||||
self.ref_counter = block.first_ref;
|
self.ref_counter = block.first_ref;
|
||||||
|
|
||||||
for (block.insts.items) |instruction| {
|
for (block.insts.items, 0..) |instruction, inst_i| {
|
||||||
|
_ = inst_i;
|
||||||
self.emitInst(&instruction, func_idx);
|
self.emitInst(&instruction, func_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,18 +555,21 @@ pub const LLVMEmitter = struct {
|
|||||||
|
|
||||||
// ── Bitwise ────────────────────────────────────────────
|
// ── Bitwise ────────────────────────────────────────────
|
||||||
.bit_and => |bin| {
|
.bit_and => |bin| {
|
||||||
const lhs = self.resolveRef(bin.lhs);
|
var lhs = self.resolveRef(bin.lhs);
|
||||||
const rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
||||||
self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "and"));
|
self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "and"));
|
||||||
},
|
},
|
||||||
.bit_or => |bin| {
|
.bit_or => |bin| {
|
||||||
const lhs = self.resolveRef(bin.lhs);
|
var lhs = self.resolveRef(bin.lhs);
|
||||||
const rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
||||||
self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "or"));
|
self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "or"));
|
||||||
},
|
},
|
||||||
.bit_xor => |bin| {
|
.bit_xor => |bin| {
|
||||||
const lhs = self.resolveRef(bin.lhs);
|
var lhs = self.resolveRef(bin.lhs);
|
||||||
const rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
||||||
self.mapRef(c.LLVMBuildXor(self.builder, lhs, rhs, "xor"));
|
self.mapRef(c.LLVMBuildXor(self.builder, lhs, rhs, "xor"));
|
||||||
},
|
},
|
||||||
.bit_not => |un| {
|
.bit_not => |un| {
|
||||||
@@ -547,13 +577,15 @@ pub const LLVMEmitter = struct {
|
|||||||
self.mapRef(c.LLVMBuildNot(self.builder, operand, "not"));
|
self.mapRef(c.LLVMBuildNot(self.builder, operand, "not"));
|
||||||
},
|
},
|
||||||
.shl => |bin| {
|
.shl => |bin| {
|
||||||
const lhs = self.resolveRef(bin.lhs);
|
var lhs = self.resolveRef(bin.lhs);
|
||||||
const rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
||||||
self.mapRef(c.LLVMBuildShl(self.builder, lhs, rhs, "shl"));
|
self.mapRef(c.LLVMBuildShl(self.builder, lhs, rhs, "shl"));
|
||||||
},
|
},
|
||||||
.shr => |bin| {
|
.shr => |bin| {
|
||||||
const lhs = self.resolveRef(bin.lhs);
|
var lhs = self.resolveRef(bin.lhs);
|
||||||
const rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
||||||
// Use arithmetic shift right for signed, logical for unsigned
|
// Use arithmetic shift right for signed, logical for unsigned
|
||||||
const result = if (isSignedType(instruction.ty))
|
const result = if (isSignedType(instruction.ty))
|
||||||
c.LLVMBuildAShr(self.builder, lhs, rhs, "ashr")
|
c.LLVMBuildAShr(self.builder, lhs, rhs, "ashr")
|
||||||
@@ -746,6 +778,15 @@ pub const LLVMEmitter = struct {
|
|||||||
if (result.asInt()) |v| {
|
if (result.asInt()) |v| {
|
||||||
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0));
|
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0));
|
||||||
return;
|
return;
|
||||||
|
} else if (result.asFloat()) |v| {
|
||||||
|
self.mapRef(c.LLVMConstReal(self.toLLVMType(instruction.ty), v));
|
||||||
|
return;
|
||||||
|
} else if (result.asBool()) |v| {
|
||||||
|
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @intFromBool(v), 0));
|
||||||
|
return;
|
||||||
|
} else if (result == .string) {
|
||||||
|
self.mapRef(self.emitStringConstant(result.string));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
}
|
}
|
||||||
@@ -770,7 +811,12 @@ pub const LLVMEmitter = struct {
|
|||||||
args[j] = self.coerceArg(args[j], param_types[j]);
|
args[j] = self.coerceArg(args[j], param_types[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (instruction.ty == .void) "" else "call");
|
var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (instruction.ty == .void) "" else "call");
|
||||||
|
// Coerce ABI return value (e.g. i64) back to IR struct type if needed
|
||||||
|
if (instruction.ty != .void and callee_func.is_extern) {
|
||||||
|
const expected_ty = self.toLLVMType(instruction.ty);
|
||||||
|
result = self.coerceArg(result, expected_ty);
|
||||||
|
}
|
||||||
self.mapRef(result);
|
self.mapRef(result);
|
||||||
},
|
},
|
||||||
.call_indirect => |call_op| {
|
.call_indirect => |call_op| {
|
||||||
@@ -813,7 +859,11 @@ pub const LLVMEmitter = struct {
|
|||||||
if (fn_params) |fp| {
|
if (fn_params) |fp| {
|
||||||
for (0..call_op.args.len) |j| {
|
for (0..call_op.args.len) |j| {
|
||||||
if (j < fp.len) {
|
if (j < fp.len) {
|
||||||
const llvm_pty = self.toLLVMType(fp[j]);
|
var llvm_pty = self.toLLVMType(fp[j]);
|
||||||
|
// Array params in fn-ptr calls decay to pointers (C ABI)
|
||||||
|
if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) {
|
||||||
|
llvm_pty = self.cached_ptr;
|
||||||
|
}
|
||||||
param_tys[j] = llvm_pty;
|
param_tys[j] = llvm_pty;
|
||||||
args[j] = self.coerceArg(args[j], llvm_pty);
|
args[j] = self.coerceArg(args[j], llvm_pty);
|
||||||
} else {
|
} else {
|
||||||
@@ -983,7 +1033,10 @@ pub const LLVMEmitter = struct {
|
|||||||
// Safety: verify base is a pointer before GEP
|
// Safety: verify base is a pointer before GEP
|
||||||
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
||||||
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
||||||
const struct_llvm_ty = self.resolveGepStructType(fa.base, instruction);
|
const struct_llvm_ty = if (fa.base_type) |bt|
|
||||||
|
self.toLLVMType(self.resolveAggregate(bt))
|
||||||
|
else
|
||||||
|
self.resolveGepStructType(fa.base, instruction);
|
||||||
const st_kind = c.LLVMGetTypeKind(struct_llvm_ty);
|
const st_kind = c.LLVMGetTypeKind(struct_llvm_ty);
|
||||||
if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) {
|
if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) {
|
||||||
const result = c.LLVMBuildStructGEP2(self.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep");
|
const result = c.LLVMBuildStructGEP2(self.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep");
|
||||||
@@ -1006,8 +1059,9 @@ pub const LLVMEmitter = struct {
|
|||||||
// Plain enum or builtin integer → integer constant
|
// Plain enum or builtin integer → integer constant
|
||||||
self.mapRef(c.LLVMConstInt(ty, ei.tag, 0));
|
self.mapRef(c.LLVMConstInt(ty, ei.tag, 0));
|
||||||
} else if (ty_kind == c.LLVMStructTypeKind) {
|
} else if (ty_kind == c.LLVMStructTypeKind) {
|
||||||
// Tagged union with no payload — store tag into union struct
|
// Tagged union with no payload — header field 0 holds the tag
|
||||||
const tag_val = c.LLVMConstInt(self.cached_i64, ei.tag, 0);
|
const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0);
|
||||||
|
const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0);
|
||||||
var result = c.LLVMGetUndef(ty);
|
var result = c.LLVMGetUndef(ty);
|
||||||
result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag");
|
result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag");
|
||||||
self.mapRef(result);
|
self.mapRef(result);
|
||||||
@@ -1015,9 +1069,10 @@ pub const LLVMEmitter = struct {
|
|||||||
self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0));
|
self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Tagged union with payload — { tag, payload_bytes }
|
// Tagged union with payload — { header, payload_bytes }
|
||||||
const union_ty = self.toLLVMType(instruction.ty);
|
const union_ty = self.toLLVMType(instruction.ty);
|
||||||
const tag_val = c.LLVMConstInt(self.cached_i64, ei.tag, 0);
|
const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0);
|
||||||
|
const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0);
|
||||||
const payload_val = self.resolveRef(ei.payload);
|
const payload_val = self.resolveRef(ei.payload);
|
||||||
|
|
||||||
// alloca union, store tag, bitcast payload area, store payload
|
// alloca union, store tag, bitcast payload area, store payload
|
||||||
@@ -1040,7 +1095,17 @@ pub const LLVMEmitter = struct {
|
|||||||
const kind = c.LLVMGetTypeKind(val_ty);
|
const kind = c.LLVMGetTypeKind(val_ty);
|
||||||
if (kind == c.LLVMStructTypeKind) {
|
if (kind == c.LLVMStructTypeKind) {
|
||||||
// Tagged union — extract field 0 (tag)
|
// Tagged union — extract field 0 (tag)
|
||||||
self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "etag"));
|
var tag = c.LLVMBuildExtractValue(self.builder, val, 0, "etag");
|
||||||
|
// Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags)
|
||||||
|
// This is essential for FFI unions where the i64 tag slot contains
|
||||||
|
// a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved)
|
||||||
|
const target_ty = self.toLLVMType(instruction.ty);
|
||||||
|
const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag));
|
||||||
|
const target_bits = c.LLVMGetIntTypeWidth(target_ty);
|
||||||
|
if (target_bits < extracted_bits) {
|
||||||
|
tag = c.LLVMBuildTrunc(self.builder, tag, target_ty, "etag.trunc");
|
||||||
|
}
|
||||||
|
self.mapRef(tag);
|
||||||
} else {
|
} else {
|
||||||
// Plain enum — the value IS the tag
|
// Plain enum — the value IS the tag
|
||||||
self.mapRef(val);
|
self.mapRef(val);
|
||||||
@@ -1071,28 +1136,34 @@ pub const LLVMEmitter = struct {
|
|||||||
const base_ty = c.LLVMTypeOf(base);
|
const base_ty = c.LLVMTypeOf(base);
|
||||||
const kind = c.LLVMGetTypeKind(base_ty);
|
const kind = c.LLVMGetTypeKind(base_ty);
|
||||||
if (kind == c.LLVMStructTypeKind) {
|
if (kind == c.LLVMStructTypeKind) {
|
||||||
// { tag, payload_bytes } — extract payload then bitcast
|
// Tagged union { header, payload_bytes } — access payload at field 1
|
||||||
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp");
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp");
|
||||||
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
||||||
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp");
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp");
|
||||||
const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ug.cast");
|
self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, payload_ptr, "ug.val"));
|
||||||
self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, typed_ptr, "ug.val"));
|
|
||||||
} else {
|
} else {
|
||||||
// Plain reinterpret
|
// Untagged union [N x i8] — alloca, store, reinterpret-load
|
||||||
self.mapRef(c.LLVMBuildBitCast(self.builder, base, result_ty, "ug.cast"));
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp");
|
||||||
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
||||||
|
self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, tmp, "ug.val"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.union_gep => |fa| {
|
.union_gep => |fa| {
|
||||||
const base_ptr = self.resolveRef(fa.base);
|
const base_ptr = self.resolveRef(fa.base);
|
||||||
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
||||||
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
||||||
const union_llvm_ty = self.resolveGepStructType(fa.base, instruction);
|
const union_llvm_ty = if (fa.base_type) |bt|
|
||||||
|
self.toLLVMType(self.resolveAggregate(bt))
|
||||||
|
else
|
||||||
|
self.resolveGepStructType(fa.base, instruction);
|
||||||
const st_kind = c.LLVMGetTypeKind(union_llvm_ty);
|
const st_kind = c.LLVMGetTypeKind(union_llvm_ty);
|
||||||
if (st_kind == c.LLVMStructTypeKind) {
|
if (st_kind == c.LLVMStructTypeKind) {
|
||||||
|
// Tagged union — payload is at field 1
|
||||||
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_llvm_ty, base_ptr, 1, "ugep.pp");
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_llvm_ty, base_ptr, 1, "ugep.pp");
|
||||||
self.mapRef(c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ugep.cast"));
|
self.mapRef(payload_ptr);
|
||||||
} else {
|
} else {
|
||||||
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
// Untagged union — data starts at offset 0
|
||||||
|
self.mapRef(base_ptr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
||||||
@@ -1317,21 +1388,19 @@ pub const LLVMEmitter = struct {
|
|||||||
_ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, "");
|
_ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, "");
|
||||||
self.advanceRefCounter();
|
self.advanceRefCounter();
|
||||||
},
|
},
|
||||||
.sqrt => {
|
.sqrt, .sin, .cos, .floor => {
|
||||||
const val = self.resolveRef(bi.args[0]);
|
const val = self.resolveRef(bi.args[0]);
|
||||||
const val_ty = c.LLVMTypeOf(val);
|
const val_ty = c.LLVMTypeOf(val);
|
||||||
const val_kind = c.LLVMGetTypeKind(val_ty);
|
const val_kind = c.LLVMGetTypeKind(val_ty);
|
||||||
if (val_kind == c.LLVMFloatTypeKind) {
|
if (val_kind == c.LLVMFloatTypeKind) {
|
||||||
// f32 → sqrtf
|
const f = self.getOrDeclareMathF32(bi.builtin);
|
||||||
const sqrtf_fn = self.getOrDeclareSqrtf();
|
|
||||||
var args = [_]c.LLVMValueRef{val};
|
var args = [_]c.LLVMValueRef{val};
|
||||||
self.mapRef(c.LLVMBuildCall2(self.builder, self.getSqrtfType(), sqrtf_fn, &args, 1, "sqrtf"));
|
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF32Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||||
} else {
|
} else {
|
||||||
// f64 → sqrt (default)
|
|
||||||
const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val;
|
const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val;
|
||||||
const sqrt_fn = self.getOrDeclareSqrt();
|
const f = self.getOrDeclareMathF64(bi.builtin);
|
||||||
var args = [_]c.LLVMValueRef{coerced};
|
var args = [_]c.LLVMValueRef{coerced};
|
||||||
self.mapRef(c.LLVMBuildCall2(self.builder, self.getSqrtType(), sqrt_fn, &args, 1, "sqrt"));
|
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF64Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.out => {
|
.out => {
|
||||||
@@ -1563,6 +1632,7 @@ pub const LLVMEmitter = struct {
|
|||||||
const field_count: u32 = switch (field_info) {
|
const field_count: u32 = switch (field_info) {
|
||||||
.@"struct" => |s| @intCast(s.fields.len),
|
.@"struct" => |s| @intCast(s.fields.len),
|
||||||
.@"union" => |u| @intCast(u.fields.len),
|
.@"union" => |u| @intCast(u.fields.len),
|
||||||
|
.tagged_union => |u| @intCast(u.fields.len),
|
||||||
.@"enum" => |e| @intCast(e.variants.len),
|
.@"enum" => |e| @intCast(e.variants.len),
|
||||||
else => 0,
|
else => 0,
|
||||||
};
|
};
|
||||||
@@ -1787,6 +1857,15 @@ pub const LLVMEmitter = struct {
|
|||||||
return self.cached_i64;
|
return self.cached_i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve through pointer types to get the underlying aggregate type.
|
||||||
|
fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId {
|
||||||
|
if (!ty.isBuiltin()) {
|
||||||
|
const info = self.ir_mod.types.get(ty);
|
||||||
|
if (info == .pointer) return info.pointer.pointee;
|
||||||
|
}
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Comparison helpers ────────────────────────────────────────────
|
// ── Comparison helpers ────────────────────────────────────────────
|
||||||
|
|
||||||
fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void {
|
fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void {
|
||||||
@@ -1813,19 +1892,27 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct types (strings, slices): compare fields individually
|
// Struct types (strings, slices, tagged unions): compare fields individually
|
||||||
if (kind == c.LLVMStructTypeKind and rhs_kind == c.LLVMStructTypeKind) {
|
if (kind == c.LLVMStructTypeKind and rhs_kind == c.LLVMStructTypeKind) {
|
||||||
const n_fields = c.LLVMCountStructElementTypes(lhs_ty);
|
const n_fields = c.LLVMCountStructElementTypes(lhs_ty);
|
||||||
if (n_fields >= 2) {
|
if (n_fields >= 2) {
|
||||||
// For {ptr, i64} structs (string/slice): compare ptr and len
|
|
||||||
// eq: (f0_l == f0_r) && (f1_l == f1_r)
|
|
||||||
// ne: (f0_l != f0_r) || (f1_l != f1_r)
|
|
||||||
const is_eq = (int_pred == c.LLVMIntEQ);
|
const is_eq = (int_pred == c.LLVMIntEQ);
|
||||||
const f0_l = c.LLVMBuildExtractValue(self.builder, lhs, 0, "sc.l0");
|
const f0_l = c.LLVMBuildExtractValue(self.builder, lhs, 0, "sc.l0");
|
||||||
const f0_r = c.LLVMBuildExtractValue(self.builder, rhs, 0, "sc.r0");
|
const f0_r = c.LLVMBuildExtractValue(self.builder, rhs, 0, "sc.r0");
|
||||||
|
const cmp0 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f0_l, f0_r, "sc.c0");
|
||||||
|
|
||||||
|
// Check if field 1 is an array (tagged union payload) — skip comparison
|
||||||
|
// For tagged unions {tag, [N x i8]}, the tag comparison alone is sufficient
|
||||||
|
const f1_ty = c.LLVMStructGetTypeAtIndex(lhs_ty, 1);
|
||||||
|
const f1_kind = c.LLVMGetTypeKind(f1_ty);
|
||||||
|
if (f1_kind == c.LLVMArrayTypeKind) {
|
||||||
|
// Tagged union: compare tag only
|
||||||
|
self.mapRef(cmp0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const f1_l = c.LLVMBuildExtractValue(self.builder, lhs, 1, "sc.l1");
|
const f1_l = c.LLVMBuildExtractValue(self.builder, lhs, 1, "sc.l1");
|
||||||
const f1_r = c.LLVMBuildExtractValue(self.builder, rhs, 1, "sc.r1");
|
const f1_r = c.LLVMBuildExtractValue(self.builder, rhs, 1, "sc.r1");
|
||||||
const cmp0 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f0_l, f0_r, "sc.c0");
|
|
||||||
const cmp1 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f1_l, f1_r, "sc.c1");
|
const cmp1 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f1_l, f1_r, "sc.c1");
|
||||||
const result = if (is_eq)
|
const result = if (is_eq)
|
||||||
c.LLVMBuildAnd(self.builder, cmp0, cmp1, "sc.and")
|
c.LLVMBuildAnd(self.builder, cmp0, cmp1, "sc.and")
|
||||||
@@ -1862,6 +1949,8 @@ pub const LLVMEmitter = struct {
|
|||||||
var rhs = self.resolveRef(bin.rhs);
|
var rhs = self.resolveRef(bin.rhs);
|
||||||
const lhs_ty = c.LLVMTypeOf(lhs);
|
const lhs_ty = c.LLVMTypeOf(lhs);
|
||||||
const kind = c.LLVMGetTypeKind(lhs_ty);
|
const kind = c.LLVMGetTypeKind(lhs_ty);
|
||||||
|
// Determine signedness from IR operand type
|
||||||
|
const is_unsigned = self.isRefUnsigned(bin.lhs) or self.isRefUnsigned(bin.rhs);
|
||||||
// Coerce operands to same type if needed
|
// Coerce operands to same type if needed
|
||||||
if (kind == c.LLVMIntegerTypeKind) {
|
if (kind == c.LLVMIntegerTypeKind) {
|
||||||
const rhs_ty = c.LLVMTypeOf(rhs);
|
const rhs_ty = c.LLVMTypeOf(rhs);
|
||||||
@@ -1869,16 +1958,21 @@ pub const LLVMEmitter = struct {
|
|||||||
if (rhs_kind == c.LLVMIntegerTypeKind) {
|
if (rhs_kind == c.LLVMIntegerTypeKind) {
|
||||||
const lw = c.LLVMGetIntTypeWidth(lhs_ty);
|
const lw = c.LLVMGetIntTypeWidth(lhs_ty);
|
||||||
const rw = c.LLVMGetIntTypeWidth(rhs_ty);
|
const rw = c.LLVMGetIntTypeWidth(rhs_ty);
|
||||||
if (lw < rw) lhs = c.LLVMBuildSExt(self.builder, lhs, rhs_ty, "cmp.ext")
|
if (is_unsigned) {
|
||||||
else if (rw < lw) rhs = c.LLVMBuildSExt(self.builder, rhs, lhs_ty, "cmp.ext");
|
if (lw < rw) lhs = c.LLVMBuildZExt(self.builder, lhs, rhs_ty, "cmp.ext")
|
||||||
|
else if (rw < lw) rhs = c.LLVMBuildZExt(self.builder, rhs, lhs_ty, "cmp.ext");
|
||||||
|
} else {
|
||||||
|
if (lw < rw) lhs = c.LLVMBuildSExt(self.builder, lhs, rhs_ty, "cmp.ext")
|
||||||
|
else if (rw < lw) rhs = c.LLVMBuildSExt(self.builder, rhs, lhs_ty, "cmp.ext");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = if (kind == c.LLVMFloatTypeKind or kind == c.LLVMDoubleTypeKind)
|
const result = if (kind == c.LLVMFloatTypeKind or kind == c.LLVMDoubleTypeKind)
|
||||||
c.LLVMBuildFCmp(self.builder, @intCast(float_pred), lhs, rhs, "fcmp")
|
c.LLVMBuildFCmp(self.builder, @intCast(float_pred), lhs, rhs, "fcmp")
|
||||||
|
else if (is_unsigned)
|
||||||
|
c.LLVMBuildICmp(self.builder, @intCast(unsigned_pred), lhs, rhs, "icmp")
|
||||||
else
|
else
|
||||||
// Default to signed comparison (most common in sx)
|
|
||||||
c.LLVMBuildICmp(self.builder, @intCast(signed_pred), lhs, rhs, "icmp");
|
c.LLVMBuildICmp(self.builder, @intCast(signed_pred), lhs, rhs, "icmp");
|
||||||
_ = unsigned_pred;
|
|
||||||
self.mapRef(result);
|
self.mapRef(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2030,24 +2124,36 @@ pub const LLVMEmitter = struct {
|
|||||||
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0);
|
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getOrDeclareSqrt(self: *LLVMEmitter) c.LLVMValueRef {
|
fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||||
if (c.LLVMGetNamedFunction(self.llvm_module, "sqrt")) |f| return f;
|
const name: [*:0]const u8 = switch (id) {
|
||||||
return c.LLVMAddFunction(self.llvm_module, "sqrt", self.getSqrtType());
|
.sqrt => "sqrt",
|
||||||
|
.sin => "sin",
|
||||||
|
.cos => "cos",
|
||||||
|
.floor => "floor",
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
if (c.LLVMGetNamedFunction(self.llvm_module, name)) |f| return f;
|
||||||
|
return c.LLVMAddFunction(self.llvm_module, name, self.getMathF64Type());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSqrtType(self: *LLVMEmitter) c.LLVMTypeRef {
|
fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||||
// sqrt(f64) → f64
|
|
||||||
var param_types = [_]c.LLVMTypeRef{self.cached_f64};
|
var param_types = [_]c.LLVMTypeRef{self.cached_f64};
|
||||||
return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0);
|
return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getOrDeclareSqrtf(self: *LLVMEmitter) c.LLVMValueRef {
|
fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||||
if (c.LLVMGetNamedFunction(self.llvm_module, "sqrtf")) |f| return f;
|
const name: [*:0]const u8 = switch (id) {
|
||||||
return c.LLVMAddFunction(self.llvm_module, "sqrtf", self.getSqrtfType());
|
.sqrt => "sqrtf",
|
||||||
|
.sin => "sinf",
|
||||||
|
.cos => "cosf",
|
||||||
|
.floor => "floorf",
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
if (c.LLVMGetNamedFunction(self.llvm_module, name)) |f| return f;
|
||||||
|
return c.LLVMAddFunction(self.llvm_module, name, self.getMathF32Type());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSqrtfType(self: *LLVMEmitter) c.LLVMTypeRef {
|
fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||||
// sqrtf(f32) → f32
|
|
||||||
var param_types = [_]c.LLVMTypeRef{self.cached_f32};
|
var param_types = [_]c.LLVMTypeRef{self.cached_f32};
|
||||||
return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0);
|
return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0);
|
||||||
}
|
}
|
||||||
@@ -2279,6 +2385,31 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Struct → Integer (C ABI coercion: store struct to memory, load as integer)
|
||||||
|
if (val_kind == c.LLVMStructTypeKind and param_kind == c.LLVMIntegerTypeKind) {
|
||||||
|
const tmp = c.LLVMBuildAlloca(self.builder, param_ty, "abi.tmp");
|
||||||
|
_ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(param_ty), tmp);
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||||
|
return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.coerce");
|
||||||
|
}
|
||||||
|
// Integer → Struct (C ABI return coercion: store integer to memory, load as struct)
|
||||||
|
if (val_kind == c.LLVMIntegerTypeKind and param_kind == c.LLVMStructTypeKind) {
|
||||||
|
const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "abi.ret.tmp");
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||||
|
return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.ret.coerce");
|
||||||
|
}
|
||||||
|
// Array → Ptr (array decay: alloca + GEP to first element)
|
||||||
|
if (val_kind == c.LLVMArrayTypeKind and param_kind == c.LLVMPointerTypeKind) {
|
||||||
|
const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "ca.arr");
|
||||||
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
||||||
|
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
|
||||||
|
var indices = [_]c.LLVMValueRef{ zero, zero };
|
||||||
|
return c.LLVMBuildGEP2(self.builder, val_ty, tmp, &indices, 2, "ca.decay");
|
||||||
|
}
|
||||||
|
// Int → Ptr (null literal: inttoptr)
|
||||||
|
if (val_kind == c.LLVMIntegerTypeKind and param_kind == c.LLVMPointerTypeKind) {
|
||||||
|
return c.LLVMBuildIntToPtr(self.builder, val, param_ty, "ca.itp");
|
||||||
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2390,19 +2521,48 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
|
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
|
||||||
},
|
},
|
||||||
.@"enum" => self.cached_i64, // enums are i64 by default
|
.@"enum" => |e| {
|
||||||
|
// Use backing type if declared (e.g. enum u32 → i32), else i64
|
||||||
|
if (e.backing_type) |bt| return self.toLLVMType(bt);
|
||||||
|
return self.cached_i64;
|
||||||
|
},
|
||||||
.@"union" => |u| {
|
.@"union" => |u| {
|
||||||
// Union: tag (i64) + largest-field payload
|
// Untagged union — just [N x i8]
|
||||||
// For simplicity, use { i64, [N x i8] } where N = max field size
|
var max_size: usize = 0;
|
||||||
var max_size: u32 = 0;
|
|
||||||
for (u.fields) |field| {
|
for (u.fields) |field| {
|
||||||
const sz = self.ir_mod.types.sizeOf(field.ty);
|
const sz = self.ir_mod.types.typeSizeBytes(field.ty);
|
||||||
if (sz > max_size) max_size = sz;
|
if (sz > max_size) max_size = sz;
|
||||||
}
|
}
|
||||||
if (max_size == 0) max_size = 8;
|
if (max_size == 0) max_size = 8;
|
||||||
|
return c.LLVMArrayType2(self.cached_i8, @intCast(max_size));
|
||||||
|
},
|
||||||
|
.tagged_union => |u| {
|
||||||
|
// Tagged union — { header, [N x i8] }
|
||||||
|
var max_size: usize = 0;
|
||||||
|
for (u.fields) |field| {
|
||||||
|
const sz = self.ir_mod.types.typeSizeBytes(field.ty);
|
||||||
|
if (sz > max_size) max_size = sz;
|
||||||
|
}
|
||||||
|
if (max_size == 0) max_size = 8;
|
||||||
|
|
||||||
|
var header_size: usize = self.ir_mod.types.typeSizeBytes(u.tag_type);
|
||||||
|
if (u.backing_type) |bt| {
|
||||||
|
const bi = self.ir_mod.types.get(bt);
|
||||||
|
if (bi == .@"struct" and bi.@"struct".fields.len > 1) {
|
||||||
|
header_size = 0;
|
||||||
|
const fields = bi.@"struct".fields;
|
||||||
|
for (fields[0 .. fields.len - 1]) |f| {
|
||||||
|
header_size += self.ir_mod.types.typeSizeBytes(f.ty);
|
||||||
|
}
|
||||||
|
const backing_payload = self.ir_mod.types.typeSizeBytes(fields[fields.len - 1].ty);
|
||||||
|
if (backing_payload > max_size) max_size = backing_payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const header_llvm = c.LLVMIntTypeInContext(self.context, @intCast(header_size * 8));
|
||||||
var field_types: [2]c.LLVMTypeRef = .{
|
var field_types: [2]c.LLVMTypeRef = .{
|
||||||
self.cached_i64, // tag
|
header_llvm,
|
||||||
c.LLVMArrayType2(self.cached_i8, max_size), // payload
|
c.LLVMArrayType2(self.cached_i8, @intCast(max_size)),
|
||||||
};
|
};
|
||||||
return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
||||||
},
|
},
|
||||||
@@ -2423,6 +2583,56 @@ pub const LLVMEmitter = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── C ABI coercion for foreign functions ──────────────────────────
|
||||||
|
//
|
||||||
|
// On ARM64 (and x86_64), the C calling convention coerces small struct
|
||||||
|
// arguments to integers for register passing:
|
||||||
|
// - String/slice {ptr, i64} → ptr (extract raw pointer)
|
||||||
|
// - Small integer struct (≤ 8 bytes, non-HFA) → i64
|
||||||
|
// - HFA (homogeneous float aggregate) → leave as-is (LLVM handles it)
|
||||||
|
|
||||||
|
fn abiCoerceParamType(self: *LLVMEmitter, ir_ty: TypeId, llvm_ty: c.LLVMTypeRef) c.LLVMTypeRef {
|
||||||
|
// String/slice → raw pointer
|
||||||
|
if (ir_ty == .string) return self.cached_ptr;
|
||||||
|
if (!ir_ty.isBuiltin()) {
|
||||||
|
const info = self.ir_mod.types.get(ir_ty);
|
||||||
|
if (info == .slice) return self.cached_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only coerce struct types
|
||||||
|
if (c.LLVMGetTypeKind(llvm_ty) != c.LLVMStructTypeKind) return llvm_ty;
|
||||||
|
|
||||||
|
// Check if it's an HFA (all float or all double fields) — leave as-is
|
||||||
|
const n_fields = c.LLVMCountStructElementTypes(llvm_ty);
|
||||||
|
if (n_fields >= 1 and n_fields <= 4) {
|
||||||
|
var all_float = true;
|
||||||
|
var all_double = true;
|
||||||
|
var fi: c_uint = 0;
|
||||||
|
while (fi < n_fields) : (fi += 1) {
|
||||||
|
const ft = c.LLVMStructGetTypeAtIndex(llvm_ty, fi);
|
||||||
|
const fk = c.LLVMGetTypeKind(ft);
|
||||||
|
if (fk != c.LLVMFloatTypeKind) all_float = false;
|
||||||
|
if (fk != c.LLVMDoubleTypeKind) all_double = false;
|
||||||
|
}
|
||||||
|
if (all_float or all_double) return llvm_ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small struct (≤ 8 bytes) → coerce to i64
|
||||||
|
const size = c.LLVMABISizeOfType(
|
||||||
|
c.LLVMGetModuleDataLayout(self.llvm_module),
|
||||||
|
llvm_ty,
|
||||||
|
);
|
||||||
|
if (size <= 8) return self.cached_i64;
|
||||||
|
|
||||||
|
// Medium struct (9-16 bytes) → coerce to [2 x i64]
|
||||||
|
if (size <= 16) {
|
||||||
|
return c.LLVMArrayType2(self.cached_i64, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large struct (> 16 bytes) → leave as-is (should be indirect, but handle later)
|
||||||
|
return llvm_ty;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Cached composite types ──────────────────────────────────────
|
// ── Cached composite types ──────────────────────────────────────
|
||||||
|
|
||||||
fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef {
|
fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||||
@@ -2457,6 +2667,25 @@ pub const LLVMEmitter = struct {
|
|||||||
|
|
||||||
// ── String constant emission ────────────────────────────────────
|
// ── String constant emission ────────────────────────────────────
|
||||||
|
|
||||||
|
/// Build a constant string { ptr, i64 } value without using the builder
|
||||||
|
/// (safe to call during global initialization, before any function body is emitted).
|
||||||
|
fn emitConstStringGlobal(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
||||||
|
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
||||||
|
defer self.alloc.free(str_z);
|
||||||
|
const len: c_uint = @intCast(str.len + 1); // include null terminator
|
||||||
|
const str_const = c.LLVMConstStringInContext(self.context, str_z.ptr, len - 1, 0);
|
||||||
|
const arr_ty = c.LLVMArrayType2(self.cached_i8, len);
|
||||||
|
const str_global_val = c.LLVMAddGlobal(self.llvm_module, arr_ty, "str.data");
|
||||||
|
c.LLVMSetInitializer(str_global_val, str_const);
|
||||||
|
c.LLVMSetGlobalConstant(str_global_val, 1);
|
||||||
|
c.LLVMSetLinkage(str_global_val, c.LLVMPrivateLinkage);
|
||||||
|
c.LLVMSetUnnamedAddress(str_global_val, c.LLVMGlobalUnnamedAddr);
|
||||||
|
// Build constant { ptr, i64 } aggregate
|
||||||
|
const len_val = c.LLVMConstInt(self.cached_i64, str.len, 0);
|
||||||
|
var fields = [_]c.LLVMValueRef{ str_global_val, len_val };
|
||||||
|
return c.LLVMConstStructInContext(self.context, &fields, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
||||||
// LLVMBuildGlobalStringPtr needs a null-terminated C string
|
// LLVMBuildGlobalStringPtr needs a null-terminated C string
|
||||||
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
||||||
@@ -2490,6 +2719,9 @@ pub const LLVMEmitter = struct {
|
|||||||
.@"union" => |u| {
|
.@"union" => |u| {
|
||||||
for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable;
|
for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable;
|
||||||
},
|
},
|
||||||
|
.tagged_union => |u| {
|
||||||
|
for (u.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable;
|
||||||
|
},
|
||||||
.@"enum" => |e| {
|
.@"enum" => |e| {
|
||||||
for (e.variants) |v| name_ids.append(self.alloc, v) catch unreachable;
|
for (e.variants) |v| name_ids.append(self.alloc, v) catch unreachable;
|
||||||
},
|
},
|
||||||
@@ -2538,6 +2770,7 @@ pub const LLVMEmitter = struct {
|
|||||||
const fields = switch (info) {
|
const fields = switch (info) {
|
||||||
.@"struct" => |s| s.fields,
|
.@"struct" => |s| s.fields,
|
||||||
.@"union" => |u| u.fields,
|
.@"union" => |u| u.fields,
|
||||||
|
.tagged_union => |u| u.fields,
|
||||||
else => &[_]TypeInfo.StructInfo.Field{},
|
else => &[_]TypeInfo.StructInfo.Field{},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2572,7 +2805,7 @@ pub const LLVMEmitter = struct {
|
|||||||
var case_values = std.ArrayList(c.LLVMValueRef).empty;
|
var case_values = std.ArrayList(c.LLVMValueRef).empty;
|
||||||
defer case_values.deinit(self.alloc);
|
defer case_values.deinit(self.alloc);
|
||||||
|
|
||||||
const is_union = info == .@"union";
|
const is_union = info == .@"union" or info == .tagged_union;
|
||||||
for (fields, 0..) |field, i| {
|
for (fields, 0..) |field, i| {
|
||||||
const case_bb = c.LLVMAppendBasicBlockInContext(self.context, current_func, "fv.case");
|
const case_bb = c.LLVMAppendBasicBlockInContext(self.context, current_func, "fv.case");
|
||||||
c.LLVMAddCase(switch_inst, c.LLVMConstInt(self.cached_i64, @intCast(i), 0), case_bb);
|
c.LLVMAddCase(switch_inst, c.LLVMConstInt(self.cached_i64, @intCast(i), 0), case_bb);
|
||||||
@@ -2651,6 +2884,8 @@ pub const LLVMEmitter = struct {
|
|||||||
if (c.LLVMVerifyModule(self.llvm_module, c.LLVMReturnStatusAction, &err_msg) != 0) {
|
if (c.LLVMVerifyModule(self.llvm_module, c.LLVMReturnStatusAction, &err_msg) != 0) {
|
||||||
if (err_msg != null) {
|
if (err_msg != null) {
|
||||||
const msg = std.mem.span(err_msg);
|
const msg = std.mem.span(err_msg);
|
||||||
|
// Dump IR to /tmp for debugging
|
||||||
|
_ = c.LLVMPrintModuleToFile(self.llvm_module, "/tmp/sx_debug.ll", null);
|
||||||
std.debug.print("LLVM verification failed: {s}\n", .{msg});
|
std.debug.print("LLVM verification failed: {s}\n", .{msg});
|
||||||
c.LLVMDisposeMessage(err_msg);
|
c.LLVMDisposeMessage(err_msg);
|
||||||
}
|
}
|
||||||
@@ -2707,6 +2942,20 @@ pub const LLVMEmitter = struct {
|
|||||||
return error.EmitFailed;
|
return error.EmitFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Check if an IR Ref's type is an unsigned integer (u8, u16, u32, u64).
|
||||||
|
fn isRefUnsigned(self: *LLVMEmitter, ref: Ref) bool {
|
||||||
|
if (ref.isNone()) return false;
|
||||||
|
const func = &self.ir_mod.functions.items[self.current_func_idx];
|
||||||
|
const ref_idx = ref.index();
|
||||||
|
for (func.blocks.items) |*block| {
|
||||||
|
const first = block.first_ref;
|
||||||
|
if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) {
|
||||||
|
const ty = block.insts.items[ref_idx - first].ty;
|
||||||
|
return ty == .u8 or ty == .u16 or ty == .u32 or ty == .u64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Type classification helpers ─────────────────────────────────────
|
// ── Type classification helpers ─────────────────────────────────────
|
||||||
|
|||||||
@@ -251,6 +251,10 @@ pub const Conversion = struct {
|
|||||||
pub const FieldAccess = struct {
|
pub const FieldAccess = struct {
|
||||||
base: Ref,
|
base: Ref,
|
||||||
field_index: u32,
|
field_index: u32,
|
||||||
|
/// The IR type of the aggregate being accessed (struct, union, etc.).
|
||||||
|
/// Used by the LLVM emitter to resolve the correct type for GEP operations
|
||||||
|
/// without guessing from LLVM value chains.
|
||||||
|
base_type: ?TypeId = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Aggregate = struct {
|
pub const Aggregate = struct {
|
||||||
@@ -286,6 +290,9 @@ pub const BuiltinCall = struct {
|
|||||||
pub const BuiltinId = enum(u16) {
|
pub const BuiltinId = enum(u16) {
|
||||||
out,
|
out,
|
||||||
sqrt,
|
sqrt,
|
||||||
|
sin,
|
||||||
|
cos,
|
||||||
|
floor,
|
||||||
size_of,
|
size_of,
|
||||||
cast,
|
cast,
|
||||||
malloc,
|
malloc,
|
||||||
|
|||||||
@@ -800,6 +800,7 @@ pub const Interpreter = struct {
|
|||||||
const fields = switch (info) {
|
const fields = switch (info) {
|
||||||
.@"struct" => |s| s.fields,
|
.@"struct" => |s| s.fields,
|
||||||
.@"union" => |u| u.fields,
|
.@"union" => |u| u.fields,
|
||||||
|
.tagged_union => |u| u.fields,
|
||||||
else => return error.CannotEvalComptime,
|
else => return error.CannotEvalComptime,
|
||||||
};
|
};
|
||||||
if (idx >= fields.len) return error.OutOfBounds;
|
if (idx >= fields.len) return error.OutOfBounds;
|
||||||
@@ -821,6 +822,7 @@ pub const Interpreter = struct {
|
|||||||
const fields = switch (info) {
|
const fields = switch (info) {
|
||||||
.@"struct" => |s| s.fields,
|
.@"struct" => |s| s.fields,
|
||||||
.@"union" => |u| u.fields,
|
.@"union" => |u| u.fields,
|
||||||
|
.tagged_union => |u| u.fields,
|
||||||
else => return error.CannotEvalComptime,
|
else => return error.CannotEvalComptime,
|
||||||
};
|
};
|
||||||
const field_ty_tag: i64 = if (idx < fields.len) @intFromEnum(fields[idx].ty) else 0;
|
const field_ty_tag: i64 = if (idx < fields.len) @intFromEnum(fields[idx].ty) else 0;
|
||||||
@@ -1225,6 +1227,21 @@ pub const Interpreter = struct {
|
|||||||
const f = val.asFloat() orelse return error.TypeError;
|
const f = val.asFloat() orelse return error.TypeError;
|
||||||
return .{ .value = .{ .float = @sqrt(f) } };
|
return .{ .value = .{ .float = @sqrt(f) } };
|
||||||
},
|
},
|
||||||
|
.sin => {
|
||||||
|
const val = frame.getRef(bi.args[0]);
|
||||||
|
const f = val.asFloat() orelse return error.TypeError;
|
||||||
|
return .{ .value = .{ .float = @sin(f) } };
|
||||||
|
},
|
||||||
|
.cos => {
|
||||||
|
const val = frame.getRef(bi.args[0]);
|
||||||
|
const f = val.asFloat() orelse return error.TypeError;
|
||||||
|
return .{ .value = .{ .float = @cos(f) } };
|
||||||
|
},
|
||||||
|
.floor => {
|
||||||
|
const val = frame.getRef(bi.args[0]);
|
||||||
|
const f = val.asFloat() orelse return error.TypeError;
|
||||||
|
return .{ .value = .{ .float = @floor(f) } };
|
||||||
|
},
|
||||||
.cast, .type_of, .alloc, .dealloc => {
|
.cast, .type_of, .alloc, .dealloc => {
|
||||||
return error.CannotEvalComptime;
|
return error.CannotEvalComptime;
|
||||||
},
|
},
|
||||||
|
|||||||
766
src/ir/lower.zig
766
src/ir/lower.zig
File diff suppressed because it is too large
Load Diff
@@ -313,14 +313,18 @@ pub const Builder = struct {
|
|||||||
return self.emit(.{ .struct_gep = .{ .base = base, .field_index = field_index } }, ty);
|
return self.emit(.{ .struct_gep = .{ .base = base, .field_index = field_index } }, ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn structGepTyped(self: *Builder, base: Ref, field_index: u32, ty: TypeId, base_type: TypeId) Ref {
|
||||||
|
return self.emit(.{ .struct_gep = .{ .base = base, .field_index = field_index, .base_type = base_type } }, ty);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Enum ops ────────────────────────────────────────────────────
|
// ── Enum ops ────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub fn enumInit(self: *Builder, tag: u32, payload: Ref, ty: TypeId) Ref {
|
pub fn enumInit(self: *Builder, tag: u32, payload: Ref, ty: TypeId) Ref {
|
||||||
return self.emit(.{ .enum_init = .{ .tag = tag, .payload = payload } }, ty);
|
return self.emit(.{ .enum_init = .{ .tag = tag, .payload = payload } }, ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enumTag(self: *Builder, val: Ref) Ref {
|
pub fn enumTag(self: *Builder, val: Ref, tag_ty: TypeId) Ref {
|
||||||
return self.emit(.{ .enum_tag = .{ .operand = val } }, .s32);
|
return self.emit(.{ .enum_tag = .{ .operand = val } }, tag_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Optional ops ────────────────────────────────────────────────
|
// ── Optional ops ────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ fn writeType(id: TypeId, tt: *const TypeTable, writer: Writer) !void {
|
|||||||
.@"struct" => |s| try writer.writeAll(tt.getString(s.name)),
|
.@"struct" => |s| try writer.writeAll(tt.getString(s.name)),
|
||||||
.@"enum" => |e| try writer.writeAll(tt.getString(e.name)),
|
.@"enum" => |e| try writer.writeAll(tt.getString(e.name)),
|
||||||
.@"union" => |u| try writer.writeAll(tt.getString(u.name)),
|
.@"union" => |u| try writer.writeAll(tt.getString(u.name)),
|
||||||
|
.tagged_union => |u| try writer.writeAll(tt.getString(u.name)),
|
||||||
.protocol => |p| try writer.writeAll(tt.getString(p.name)),
|
.protocol => |p| try writer.writeAll(tt.getString(p.name)),
|
||||||
.pointer => |p| {
|
.pointer => |p| {
|
||||||
try writer.writeByte('*');
|
try writer.writeByte('*');
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ fn resolveNamedType(name: []const u8, kind: NamedKind, table: *TypeTable) TypeId
|
|||||||
return switch (kind) {
|
return switch (kind) {
|
||||||
.@"struct" => table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }),
|
.@"struct" => table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }),
|
||||||
.@"enum" => table.intern(.{ .@"enum" = .{ .name = name_id, .variants = &.{} } }),
|
.@"enum" => table.intern(.{ .@"enum" = .{ .name = name_id, .variants = &.{} } }),
|
||||||
.@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{}, .tag_type = null } }),
|
.@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{} } }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,10 +367,46 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable) TypeId {
|
|||||||
.ty = field_ty,
|
.ty = field_ty,
|
||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
const info: TypeInfo = .{ .@"union" = .{
|
// Resolve backing type and tag type from enum struct
|
||||||
|
// e.g. enum struct { tag: u32; _: u32; payload: [30]u32; } { ... }
|
||||||
|
var backing_type: ?TypeId = null;
|
||||||
|
var tag_type: ?TypeId = null;
|
||||||
|
if (ed.backing_type) |bt| {
|
||||||
|
const backing_ty = resolveAstType(bt, table);
|
||||||
|
backing_type = backing_ty;
|
||||||
|
// Extract tag type from first field of backing struct
|
||||||
|
const backing_info = table.get(backing_ty);
|
||||||
|
if (backing_info == .@"struct") {
|
||||||
|
if (backing_info.@"struct".fields.len > 0) {
|
||||||
|
tag_type = backing_info.@"struct".fields[0].ty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build explicit tag values from variant_values (e.g., quit :: 0x100)
|
||||||
|
var explicit_tag_vals: ?[]const i64 = null;
|
||||||
|
if (ed.variant_values.len > 0) {
|
||||||
|
var vals = std.ArrayList(i64).empty;
|
||||||
|
for (0..ed.variant_names.len) |i| {
|
||||||
|
if (i < ed.variant_values.len) {
|
||||||
|
if (ed.variant_values[i]) |vv| {
|
||||||
|
if (vv.data == .int_literal) {
|
||||||
|
vals.append(alloc, vv.data.int_literal.value) catch unreachable;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vals.append(alloc, @intCast(i)) catch unreachable;
|
||||||
|
}
|
||||||
|
explicit_tag_vals = vals.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info: TypeInfo = .{ .tagged_union = .{
|
||||||
.name = name_id,
|
.name = name_id,
|
||||||
.fields = fields.items,
|
.fields = fields.items,
|
||||||
.tag_type = null,
|
.tag_type = tag_type orelse .s64, // enum unions are always tagged (default i64)
|
||||||
|
.backing_type = backing_type,
|
||||||
|
.explicit_tag_values = explicit_tag_vals,
|
||||||
} };
|
} };
|
||||||
const id = table.intern(info);
|
const id = table.intern(info);
|
||||||
table.update(id, info);
|
table.update(id, info);
|
||||||
@@ -414,11 +450,21 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable) TypeId {
|
|||||||
}
|
}
|
||||||
explicit_vals = vals.items;
|
explicit_vals = vals.items;
|
||||||
}
|
}
|
||||||
|
// Resolve backing type for sized enums (e.g. enum u32 { ... })
|
||||||
|
var enum_backing: ?TypeId = null;
|
||||||
|
if (ed.backing_type) |bt| {
|
||||||
|
// Only use simple backing types (u8, u16, u32, etc.), not struct backing (enum struct)
|
||||||
|
if (bt.data != .struct_decl) {
|
||||||
|
enum_backing = resolveAstType(bt, table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const info: TypeInfo = .{ .@"enum" = .{
|
const info: TypeInfo = .{ .@"enum" = .{
|
||||||
.name = name_id,
|
.name = name_id,
|
||||||
.variants = variants.items,
|
.variants = variants.items,
|
||||||
.is_flags = ed.is_flags,
|
.is_flags = ed.is_flags,
|
||||||
.explicit_values = explicit_vals,
|
.explicit_values = explicit_vals,
|
||||||
|
.backing_type = enum_backing,
|
||||||
} };
|
} };
|
||||||
const id = table.intern(info);
|
const id = table.intern(info);
|
||||||
table.update(id, info);
|
table.update(id, info);
|
||||||
@@ -465,7 +511,6 @@ fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable) TypeId {
|
|||||||
const info: TypeInfo = .{ .@"union" = .{
|
const info: TypeInfo = .{ .@"union" = .{
|
||||||
.name = name_id,
|
.name = name_id,
|
||||||
.fields = fields.items,
|
.fields = fields.items,
|
||||||
.tag_type = null,
|
|
||||||
} };
|
} };
|
||||||
const id = table.intern(info);
|
const id = table.intern(info);
|
||||||
table.update(id, info);
|
table.update(id, info);
|
||||||
|
|||||||
172
src/ir/types.zig
172
src/ir/types.zig
@@ -56,6 +56,7 @@ pub const TypeInfo = union(enum) {
|
|||||||
@"struct": StructInfo,
|
@"struct": StructInfo,
|
||||||
@"enum": EnumInfo,
|
@"enum": EnumInfo,
|
||||||
@"union": UnionInfo,
|
@"union": UnionInfo,
|
||||||
|
tagged_union: TaggedUnionInfo,
|
||||||
array: ArrayInfo,
|
array: ArrayInfo,
|
||||||
slice: SliceInfo,
|
slice: SliceInfo,
|
||||||
pointer: PointerInfo,
|
pointer: PointerInfo,
|
||||||
@@ -84,12 +85,20 @@ pub const TypeInfo = union(enum) {
|
|||||||
variants: []const StringId,
|
variants: []const StringId,
|
||||||
is_flags: bool = false,
|
is_flags: bool = false,
|
||||||
explicit_values: ?[]const i64 = null, // for flags (power-of-2) or custom values
|
explicit_values: ?[]const i64 = null, // for flags (power-of-2) or custom values
|
||||||
|
backing_type: ?TypeId = null, // e.g. u32 for `enum u32 { ... }`
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UnionInfo = struct {
|
pub const UnionInfo = struct {
|
||||||
name: StringId,
|
name: StringId,
|
||||||
fields: []const StructInfo.Field,
|
fields: []const StructInfo.Field,
|
||||||
tag_type: ?TypeId, // tagged union enum type, null if untagged
|
};
|
||||||
|
|
||||||
|
pub const TaggedUnionInfo = struct {
|
||||||
|
name: StringId,
|
||||||
|
fields: []const StructInfo.Field,
|
||||||
|
tag_type: TypeId, // tag integer type (e.g. .u32, .s64)
|
||||||
|
backing_type: ?TypeId = null, // enum struct backing (e.g. { tag: u32; _: u32; payload: [30]u32; })
|
||||||
|
explicit_tag_values: ?[]const i64 = null, // explicit variant values (e.g., quit :: 0x100)
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ArrayInfo = struct {
|
pub const ArrayInfo = struct {
|
||||||
@@ -290,6 +299,7 @@ pub const TypeTable = struct {
|
|||||||
const n: ?StringId = switch (info) {
|
const n: ?StringId = switch (info) {
|
||||||
.@"struct" => |s| s.name,
|
.@"struct" => |s| s.name,
|
||||||
.@"union" => |u| u.name,
|
.@"union" => |u| u.name,
|
||||||
|
.tagged_union => |u| u.name,
|
||||||
.@"enum" => |e| e.name,
|
.@"enum" => |e| e.name,
|
||||||
else => null,
|
else => null,
|
||||||
};
|
};
|
||||||
@@ -358,15 +368,27 @@ pub const TypeTable = struct {
|
|||||||
return if (total == 0) 8 else total;
|
return if (total == 0) 8 else total;
|
||||||
},
|
},
|
||||||
.@"union" => |u| {
|
.@"union" => |u| {
|
||||||
// Size of union = tag + max(field sizes)
|
|
||||||
var max_field: u32 = 0;
|
var max_field: u32 = 0;
|
||||||
for (u.fields) |f| {
|
for (u.fields) |f| {
|
||||||
const sz = self.sizeOf(f.ty);
|
const sz = self.sizeOf(f.ty);
|
||||||
if (sz > max_field) max_field = sz;
|
if (sz > max_field) max_field = sz;
|
||||||
}
|
}
|
||||||
return 8 + @max(max_field, 8);
|
return @max(max_field, 8);
|
||||||
|
},
|
||||||
|
.tagged_union => |u| {
|
||||||
|
if (u.backing_type) |bt| return self.sizeOf(bt);
|
||||||
|
var max_field: u32 = 0;
|
||||||
|
for (u.fields) |f| {
|
||||||
|
const sz = self.sizeOf(f.ty);
|
||||||
|
if (sz > max_field) max_field = sz;
|
||||||
|
}
|
||||||
|
const tag_sz = @as(u32, @intCast(self.typeSizeBytes(u.tag_type)));
|
||||||
|
return tag_sz + @max(max_field, 8);
|
||||||
|
},
|
||||||
|
.@"enum" => |e| {
|
||||||
|
if (e.backing_type) |bt| return self.sizeOf(bt);
|
||||||
|
return 8;
|
||||||
},
|
},
|
||||||
.@"enum" => 8, // plain enums are just integer tags
|
|
||||||
.tuple => |t| {
|
.tuple => |t| {
|
||||||
var total: u32 = 0;
|
var total: u32 = 0;
|
||||||
for (t.fields) |f| total += @max(self.sizeOf(f), 8);
|
for (t.fields) |f| total += @max(self.sizeOf(f), 8);
|
||||||
@@ -376,6 +398,145 @@ pub const TypeTable = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute the ABI size in bytes for a type, matching LLVM's struct layout rules.
|
||||||
|
/// This is the authoritative size computation used for closure env sizing and
|
||||||
|
/// verified against LLVMABISizeOfType.
|
||||||
|
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||||
|
if (ty == .void) return 0;
|
||||||
|
if (ty == .bool) return 1;
|
||||||
|
if (ty == .u8 or ty == .s8) return 1;
|
||||||
|
if (ty == .u16 or ty == .s16) return 2;
|
||||||
|
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||||
|
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||||
|
if (ty == .string) return 16; // {ptr, i64}
|
||||||
|
if (ty.isBuiltin()) return 8; // default for unknown builtins
|
||||||
|
const info = self.get(ty);
|
||||||
|
return switch (info) {
|
||||||
|
.pointer, .many_pointer, .function => 8,
|
||||||
|
.slice => 16, // {ptr, i64}
|
||||||
|
.closure => 16, // {fn_ptr, env_ptr}
|
||||||
|
.optional => |o| blk: {
|
||||||
|
const child_info = self.get(o.child);
|
||||||
|
if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
|
||||||
|
break :blk 8;
|
||||||
|
if (child_info == .closure)
|
||||||
|
break :blk 16; // {fn_ptr, env_ptr}
|
||||||
|
const cs = self.typeSizeBytes(o.child);
|
||||||
|
const ca = self.typeAlignBytes(o.child);
|
||||||
|
// { T, i1 } — i1 goes right after T, then pad to struct alignment
|
||||||
|
const unpadded = cs + 1;
|
||||||
|
break :blk (unpadded + ca - 1) & ~(ca - 1);
|
||||||
|
},
|
||||||
|
.@"struct" => |s| blk: {
|
||||||
|
var offset: usize = 0;
|
||||||
|
var max_a: usize = 1;
|
||||||
|
for (s.fields) |f| {
|
||||||
|
const fs = self.typeSizeBytes(f.ty);
|
||||||
|
const fa = self.typeAlignBytes(f.ty);
|
||||||
|
if (fa > max_a) max_a = fa;
|
||||||
|
offset = (offset + fa - 1) & ~(fa - 1);
|
||||||
|
offset += fs;
|
||||||
|
}
|
||||||
|
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
|
||||||
|
},
|
||||||
|
.@"union" => |u| blk: {
|
||||||
|
var max_payload: usize = 0;
|
||||||
|
for (u.fields) |f| {
|
||||||
|
const fs = self.typeSizeBytes(f.ty);
|
||||||
|
if (fs > max_payload) max_payload = fs;
|
||||||
|
}
|
||||||
|
break :blk if (max_payload == 0) 8 else max_payload;
|
||||||
|
},
|
||||||
|
.tagged_union => |u| blk: {
|
||||||
|
if (u.backing_type) |bt| break :blk self.typeSizeBytes(bt);
|
||||||
|
var max_payload: usize = 0;
|
||||||
|
for (u.fields) |f| {
|
||||||
|
const fs = self.typeSizeBytes(f.ty);
|
||||||
|
if (fs > max_payload) max_payload = fs;
|
||||||
|
}
|
||||||
|
const tag_size = self.typeSizeBytes(u.tag_type);
|
||||||
|
const raw = max_payload + tag_size;
|
||||||
|
break :blk (raw + 7) & ~@as(usize, 7);
|
||||||
|
},
|
||||||
|
.array => |a| blk: {
|
||||||
|
const elem_size = self.typeSizeBytes(a.element);
|
||||||
|
break :blk elem_size * @as(usize, @intCast(a.length));
|
||||||
|
},
|
||||||
|
.vector => |v| blk: {
|
||||||
|
const elem_size = self.typeSizeBytes(v.element);
|
||||||
|
const raw = elem_size * @as(usize, @intCast(v.length));
|
||||||
|
// LLVM vectors round ABI size up to next power of 2
|
||||||
|
break :blk std.math.ceilPowerOfTwo(usize, raw) catch raw;
|
||||||
|
},
|
||||||
|
.tuple => |t| blk: {
|
||||||
|
var offset: usize = 0;
|
||||||
|
var max_a: usize = 1;
|
||||||
|
for (t.fields) |f| {
|
||||||
|
const fs = self.typeSizeBytes(f);
|
||||||
|
const fa = self.typeAlignBytes(f);
|
||||||
|
if (fa > max_a) max_a = fa;
|
||||||
|
offset = (offset + fa - 1) & ~(fa - 1);
|
||||||
|
offset += fs;
|
||||||
|
}
|
||||||
|
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
|
||||||
|
},
|
||||||
|
.any => 16,
|
||||||
|
.protocol => 16,
|
||||||
|
.@"enum" => |e| {
|
||||||
|
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
else => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
|
||||||
|
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||||
|
if (ty == .void) return 1;
|
||||||
|
if (ty == .bool) return 1;
|
||||||
|
if (ty == .u8 or ty == .s8) return 1;
|
||||||
|
if (ty == .u16 or ty == .s16) return 2;
|
||||||
|
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
|
||||||
|
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
|
||||||
|
if (ty == .string) return 8;
|
||||||
|
if (ty.isBuiltin()) return 8;
|
||||||
|
const info = self.get(ty);
|
||||||
|
return switch (info) {
|
||||||
|
.pointer, .many_pointer, .function => 8,
|
||||||
|
.slice, .closure => 8,
|
||||||
|
.optional => |o| blk: {
|
||||||
|
const child_info = self.get(o.child);
|
||||||
|
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure)
|
||||||
|
break :blk 8;
|
||||||
|
break :blk self.typeAlignBytes(o.child);
|
||||||
|
},
|
||||||
|
.@"struct" => |s| blk: {
|
||||||
|
var max_a: usize = 1;
|
||||||
|
for (s.fields) |f| {
|
||||||
|
const fa = self.typeAlignBytes(f.ty);
|
||||||
|
if (fa > max_a) max_a = fa;
|
||||||
|
}
|
||||||
|
break :blk max_a;
|
||||||
|
},
|
||||||
|
.@"union", .tagged_union => 8,
|
||||||
|
.@"enum" => |e| {
|
||||||
|
if (e.backing_type) |bt| return self.typeAlignBytes(bt);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
.array => |a| self.typeAlignBytes(a.element),
|
||||||
|
.vector => |v| self.typeAlignBytes(v.element),
|
||||||
|
.tuple => |t| blk: {
|
||||||
|
var max_a: usize = 1;
|
||||||
|
for (t.fields) |f| {
|
||||||
|
const fa = self.typeAlignBytes(f);
|
||||||
|
if (fa > max_a) max_a = fa;
|
||||||
|
}
|
||||||
|
break :blk max_a;
|
||||||
|
},
|
||||||
|
else => 8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Intern a string into the pool.
|
/// Intern a string into the pool.
|
||||||
pub fn internString(self: *TypeTable, str: []const u8) StringId {
|
pub fn internString(self: *TypeTable, str: []const u8) StringId {
|
||||||
return self.strings.intern(self.alloc, str);
|
return self.strings.intern(self.alloc, str);
|
||||||
@@ -412,6 +573,7 @@ pub const TypeTable = struct {
|
|||||||
.@"struct" => |s| self.getString(s.name),
|
.@"struct" => |s| self.getString(s.name),
|
||||||
.@"enum" => |e| self.getString(e.name),
|
.@"enum" => |e| self.getString(e.name),
|
||||||
.@"union" => |u| self.getString(u.name),
|
.@"union" => |u| self.getString(u.name),
|
||||||
|
.tagged_union => |u| self.getString(u.name),
|
||||||
.protocol => |p| self.getString(p.name),
|
.protocol => |p| self.getString(p.name),
|
||||||
else => "?",
|
else => "?",
|
||||||
};
|
};
|
||||||
@@ -471,6 +633,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
|||||||
.@"struct" => |s| h.update(std.mem.asBytes(&s.name)),
|
.@"struct" => |s| h.update(std.mem.asBytes(&s.name)),
|
||||||
.@"enum" => |e| h.update(std.mem.asBytes(&e.name)),
|
.@"enum" => |e| h.update(std.mem.asBytes(&e.name)),
|
||||||
.@"union" => |u| h.update(std.mem.asBytes(&u.name)),
|
.@"union" => |u| h.update(std.mem.asBytes(&u.name)),
|
||||||
|
.tagged_union => |u| h.update(std.mem.asBytes(&u.name)),
|
||||||
.protocol => |p| h.update(std.mem.asBytes(&p.name)),
|
.protocol => |p| h.update(std.mem.asBytes(&p.name)),
|
||||||
.tuple => |t| {
|
.tuple => |t| {
|
||||||
for (t.fields) |f| h.update(std.mem.asBytes(&f));
|
for (t.fields) |f| h.update(std.mem.asBytes(&f));
|
||||||
@@ -513,6 +676,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
|||||||
.@"struct" => |s| s.name == b.@"struct".name,
|
.@"struct" => |s| s.name == b.@"struct".name,
|
||||||
.@"enum" => |e| e.name == b.@"enum".name,
|
.@"enum" => |e| e.name == b.@"enum".name,
|
||||||
.@"union" => |u| u.name == b.@"union".name,
|
.@"union" => |u| u.name == b.@"union".name,
|
||||||
|
.tagged_union => |u| u.name == b.tagged_union.name,
|
||||||
.protocol => |p| p.name == b.protocol.name,
|
.protocol => |p| p.name == b.protocol.name,
|
||||||
.tuple => |t| {
|
.tuple => |t| {
|
||||||
const u = b.tuple;
|
const u = b.tuple;
|
||||||
|
|||||||
138
src/main.zig
138
src/main.zig
@@ -1,9 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sx = @import("sx");
|
const sx = @import("sx");
|
||||||
|
|
||||||
/// Feature flag: use the IR pipeline (parse → lower → IR → LLVM) instead of AST-based codegen.
|
|
||||||
const USE_IR_PIPELINE = true;
|
|
||||||
|
|
||||||
pub fn main(init: std.process.Init) !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
const allocator = init.arena.allocator();
|
const allocator = init.arena.allocator();
|
||||||
const io = init.io;
|
const io = init.io;
|
||||||
@@ -24,7 +21,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
// Parse flags and positional arguments
|
// Parse flags and positional arguments
|
||||||
var input_path: ?[]const u8 = null;
|
var input_path: ?[]const u8 = null;
|
||||||
var target_config = sx.codegen.TargetConfig{};
|
var target_config = sx.target.TargetConfig{};
|
||||||
var lib_paths = std.ArrayList([]const u8).empty;
|
var lib_paths = std.ArrayList([]const u8).empty;
|
||||||
var show_timing: bool = false;
|
var show_timing: bool = false;
|
||||||
var explicit_opt: bool = false;
|
var explicit_opt: bool = false;
|
||||||
@@ -140,25 +137,12 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache MISS — codegen + emit .o to memory (verify skipped: JIT catches errors)
|
// Cache MISS — codegen + emit .o to memory (verify skipped: JIT catches errors)
|
||||||
if (USE_IR_PIPELINE) {
|
comp.generateCode() catch { comp.renderErrors(); return; };
|
||||||
comp.generateCodeViaIR() catch { comp.renderErrors(); return; };
|
|
||||||
} else {
|
|
||||||
comp.generateCode() catch { comp.renderErrors(); return; };
|
|
||||||
}
|
|
||||||
timer.record("codegen");
|
timer.record("codegen");
|
||||||
|
|
||||||
timer.mark();
|
timer.mark();
|
||||||
const buf = if (USE_IR_PIPELINE) blk2: {
|
comp.ir_emitter.?.verifyWithMessage() catch return;
|
||||||
comp.ir_emitter.?.verifyWithMessage() catch return;
|
const buf = comp.ir_emitter.?.emitObjectToMemory() catch return;
|
||||||
break :blk2 comp.ir_emitter.?.emitObjectToMemory() catch return;
|
|
||||||
} else
|
|
||||||
(emit_blk: {
|
|
||||||
var cg = &comp.cg.?;
|
|
||||||
break :emit_blk cg.emitObjectToMemory() catch {
|
|
||||||
comp.renderErrors();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
timer.record("emit");
|
timer.record("emit");
|
||||||
|
|
||||||
// Save .o to cache (extract data before JIT takes ownership)
|
// Save .o to cache (extract data before JIT takes ownership)
|
||||||
@@ -175,10 +159,25 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
defer c_handle.unload(io);
|
defer c_handle.unload(io);
|
||||||
timer.record("c-import");
|
timer.record("c-import");
|
||||||
|
|
||||||
|
// dlopen #library dependencies so JIT can resolve foreign symbols
|
||||||
|
const libs = extractLibraries(allocator, root) catch return;
|
||||||
|
var lib_handles = std.ArrayList(*anyopaque).empty;
|
||||||
|
defer {
|
||||||
|
for (lib_handles.items) |h| _ = std.c.dlclose(h);
|
||||||
|
}
|
||||||
|
for (libs) |lib_name| {
|
||||||
|
if (loadLibrary(allocator, lib_name, target_config.lib_paths)) |handle| {
|
||||||
|
lib_handles.append(allocator, handle) catch {};
|
||||||
|
} else {
|
||||||
|
const e = std.c.dlerror();
|
||||||
|
if (e) |msg| std.debug.print("warning: could not load library '{s}': {s}\n", .{ lib_name, std.mem.span(msg) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// JIT from precompiled object (relocation only, no IR compilation)
|
// JIT from precompiled object (relocation only, no IR compilation)
|
||||||
sx.llvm_api.initNativeTarget();
|
sx.llvm_api.initNativeTarget();
|
||||||
timer.mark();
|
timer.mark();
|
||||||
const exit_code = sx.codegen.CodeGen.runJITFromObject(obj_buf) catch {
|
const exit_code = sx.target.runJITFromObject(obj_buf) catch {
|
||||||
// JIT failed — fall back to AOT
|
// JIT failed — fall back to AOT
|
||||||
timer.record("jit-fail");
|
timer.record("jit-fail");
|
||||||
runAOT(allocator, io, path, target_config, &timer, enable_cache) catch return;
|
runAOT(allocator, io, path, target_config, &timer, enable_cache) catch return;
|
||||||
@@ -212,7 +211,7 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
|
|||||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseOptLevel(s: []const u8) ?sx.codegen.TargetConfig.OptLevel {
|
fn parseOptLevel(s: []const u8) ?sx.target.TargetConfig.OptLevel {
|
||||||
if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none;
|
if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none;
|
||||||
if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less;
|
if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less;
|
||||||
if (std.mem.eql(u8, s, "default") or std.mem.eql(u8, s, "2")) return .default;
|
if (std.mem.eql(u8, s, "default") or std.mem.eql(u8, s, "2")) return .default;
|
||||||
@@ -294,7 +293,7 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8)
|
|||||||
return try allocator.dupeZ(u8, source_bytes);
|
return try allocator.dupeZ(u8, source_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing) !sx.core.Compilation {
|
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing) !sx.core.Compilation {
|
||||||
timer.mark();
|
timer.mark();
|
||||||
const source = try readSource(allocator, io, input_path);
|
const source = try readSource(allocator, io, input_path);
|
||||||
timer.record("read");
|
timer.record("read");
|
||||||
@@ -311,20 +310,11 @@ fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const
|
|||||||
timer.record("imports");
|
timer.record("imports");
|
||||||
|
|
||||||
timer.mark();
|
timer.mark();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
|
||||||
comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
} else {
|
|
||||||
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
timer.record("codegen");
|
timer.record("codegen");
|
||||||
|
|
||||||
timer.mark();
|
timer.mark();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
|
||||||
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
|
|
||||||
} else {
|
|
||||||
var cg = &comp.cg.?;
|
|
||||||
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
timer.record("verify");
|
timer.record("verify");
|
||||||
|
|
||||||
return comp;
|
return comp;
|
||||||
@@ -348,18 +338,14 @@ fn dumpSxIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !v
|
|||||||
std.debug.print("{s}", .{result.items});
|
std.debug.print("{s}", .{result.items});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
|
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void {
|
||||||
var timer = Timing.init(false);
|
var timer = Timing.init(false);
|
||||||
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
|
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
|
||||||
defer comp.deinit();
|
defer comp.deinit();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.ir_emitter.?.printIR();
|
||||||
comp.ir_emitter.?.printIR();
|
|
||||||
} else {
|
|
||||||
comp.cg.?.printIR();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
|
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void {
|
||||||
var timer = Timing.init(false);
|
var timer = Timing.init(false);
|
||||||
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
|
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
|
||||||
defer comp.deinit();
|
defer comp.deinit();
|
||||||
@@ -368,21 +354,17 @@ fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, tar
|
|||||||
break :blk try std.fmt.allocPrint(allocator, "{s}.s", .{name});
|
break :blk try std.fmt.allocPrint(allocator, "{s}.s", .{name});
|
||||||
};
|
};
|
||||||
const asm_path_z = try allocator.dupeZ(u8, asm_path);
|
const asm_path_z = try allocator.dupeZ(u8, asm_path);
|
||||||
if (USE_IR_PIPELINE) {
|
comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError;
|
||||||
comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError;
|
|
||||||
} else {
|
|
||||||
comp.cg.?.emitAssembly(asm_path_z.ptr) catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
std.debug.print("emitted: {s}\n", .{asm_path});
|
std.debug.print("emitted: {s}\n", .{asm_path});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, show_timing: bool, enable_cache: bool) !void {
|
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, show_timing: bool, enable_cache: bool) !void {
|
||||||
var timer = Timing.init(show_timing);
|
var timer = Timing.init(show_timing);
|
||||||
try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, enable_cache);
|
try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, enable_cache);
|
||||||
timer.printAll();
|
timer.printAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void {
|
fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void {
|
||||||
// Phase A: read + parse + resolveImports (fast: ~0.5ms)
|
// Phase A: read + parse + resolveImports (fast: ~0.5ms)
|
||||||
timer.mark();
|
timer.mark();
|
||||||
const source = try readSource(allocator, io, input_path);
|
const source = try readSource(allocator, io, input_path);
|
||||||
@@ -430,29 +412,15 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
|||||||
} else {
|
} else {
|
||||||
// Cache MISS — full codegen + emit
|
// Cache MISS — full codegen + emit
|
||||||
timer.mark();
|
timer.mark();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
|
||||||
comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
} else {
|
|
||||||
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
timer.record("codegen");
|
timer.record("codegen");
|
||||||
|
|
||||||
timer.mark();
|
timer.mark();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
|
||||||
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
|
|
||||||
} else {
|
|
||||||
var cg = &comp.cg.?;
|
|
||||||
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
timer.record("verify");
|
timer.record("verify");
|
||||||
|
|
||||||
timer.mark();
|
timer.mark();
|
||||||
if (USE_IR_PIPELINE) {
|
comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError;
|
||||||
comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError;
|
|
||||||
} else {
|
|
||||||
var cg = &comp.cg.?;
|
|
||||||
cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; };
|
|
||||||
}
|
|
||||||
timer.record("emit");
|
timer.record("emit");
|
||||||
|
|
||||||
// Save .o to cache
|
// Save .o to cache
|
||||||
@@ -471,7 +439,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
|||||||
|
|
||||||
// Link (sx .o + C .o files)
|
// Link (sx .o + C .o files)
|
||||||
timer.mark();
|
timer.mark();
|
||||||
sx.codegen.CodeGen.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch {
|
sx.target.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch {
|
||||||
std.debug.print("error: linking failed\n", .{});
|
std.debug.print("error: linking failed\n", .{});
|
||||||
return error.CompileError;
|
return error.CompileError;
|
||||||
};
|
};
|
||||||
@@ -489,7 +457,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void {
|
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void {
|
||||||
const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp";
|
const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp";
|
||||||
try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, enable_cache);
|
try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, enable_cache);
|
||||||
defer {
|
defer {
|
||||||
@@ -518,7 +486,7 @@ fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, targ
|
|||||||
|
|
||||||
// --- Cache helpers ---
|
// --- Cache helpers ---
|
||||||
|
|
||||||
fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.codegen.TargetConfig) u64 {
|
fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.target.TargetConfig) u64 {
|
||||||
const Wyhash = std.hash.Wyhash;
|
const Wyhash = std.hash.Wyhash;
|
||||||
var key = Wyhash.hash(0, source);
|
var key = Wyhash.hash(0, source);
|
||||||
|
|
||||||
@@ -583,6 +551,40 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c
|
|||||||
return try libs.toOwnedSlice(allocator);
|
return try libs.toOwnedSlice(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to dlopen a library by name, searching user paths, host paths, and common naming conventions.
|
||||||
|
fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_paths: []const []const u8) ?*anyopaque {
|
||||||
|
const is_macos = comptime @import("builtin").os.tag == .macos;
|
||||||
|
const suffixes: []const []const u8 = if (is_macos) &.{ ".dylib", ".so" } else &.{ ".so", ".dylib" };
|
||||||
|
|
||||||
|
// Search paths: user-supplied first, then host defaults
|
||||||
|
const search_paths = comptime blk: {
|
||||||
|
var paths: []const []const u8 = &.{};
|
||||||
|
for (sx.target.host_lib_paths) |p| {
|
||||||
|
paths = paths ++ .{p};
|
||||||
|
}
|
||||||
|
break :blk paths;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try each path with each suffix
|
||||||
|
const all_paths = [_][]const []const u8{ user_lib_paths, search_paths };
|
||||||
|
for (&all_paths) |paths| {
|
||||||
|
for (paths) |dir| {
|
||||||
|
for (suffixes) |sfx| {
|
||||||
|
const full = std.fmt.allocPrintSentinel(allocator, "{s}/lib{s}{s}", .{ dir, lib_name, sfx }, 0) catch continue;
|
||||||
|
if (std.c.dlopen(full.ptr, .{ .NOW = true })) |h| return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: bare name (let dlopen search its default paths)
|
||||||
|
for (suffixes) |sfx| {
|
||||||
|
const bare = std.fmt.allocPrintSentinel(allocator, "lib{s}{s}", .{ lib_name, sfx }, 0) catch continue;
|
||||||
|
if (std.c.dlopen(bare.ptr, .{ .NOW = true })) |h| return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Simple timing helper — records stage durations and prints a summary table.
|
// Simple timing helper — records stage durations and prints a summary table.
|
||||||
const Timing = struct {
|
const Timing = struct {
|
||||||
const max_entries = 16;
|
const max_entries = 16;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub const lexer = @import("lexer.zig");
|
|||||||
pub const ast = @import("ast.zig");
|
pub const ast = @import("ast.zig");
|
||||||
pub const parser = @import("parser.zig");
|
pub const parser = @import("parser.zig");
|
||||||
pub const types = @import("types.zig");
|
pub const types = @import("types.zig");
|
||||||
pub const codegen = @import("codegen.zig");
|
pub const target = @import("target.zig");
|
||||||
pub const builtins = @import("builtins.zig");
|
pub const builtins = @import("builtins.zig");
|
||||||
pub const errors = @import("errors.zig");
|
pub const errors = @import("errors.zig");
|
||||||
pub const sema = @import("sema.zig");
|
pub const sema = @import("sema.zig");
|
||||||
|
|||||||
206
src/target.zig
Normal file
206
src/target.zig
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const llvm = @import("llvm_api.zig");
|
||||||
|
const c = llvm.c;
|
||||||
|
|
||||||
|
pub const TargetConfig = struct {
|
||||||
|
/// Target triple (e.g. "aarch64-apple-darwin"). Null = host default.
|
||||||
|
triple: ?[*:0]const u8 = null,
|
||||||
|
/// CPU name (e.g. "generic", "apple-m1"). Null = "generic".
|
||||||
|
cpu: ?[*:0]const u8 = null,
|
||||||
|
/// CPU features string (e.g. "+avx2"). Null = "".
|
||||||
|
features: ?[*:0]const u8 = null,
|
||||||
|
/// Optimization level.
|
||||||
|
opt_level: OptLevel = .default,
|
||||||
|
/// Library search paths (-L flags).
|
||||||
|
lib_paths: []const []const u8 = &.{},
|
||||||
|
/// Output path override.
|
||||||
|
output_path: ?[]const u8 = null,
|
||||||
|
/// Linker command (null = "cc" on Unix, "link.exe" on Windows).
|
||||||
|
linker: ?[]const u8 = null,
|
||||||
|
/// Sysroot for cross-compilation (passed as --sysroot to linker).
|
||||||
|
sysroot: ?[]const u8 = null,
|
||||||
|
|
||||||
|
pub const OptLevel = enum {
|
||||||
|
none,
|
||||||
|
less,
|
||||||
|
default,
|
||||||
|
aggressive,
|
||||||
|
|
||||||
|
pub fn toLLVM(self: OptLevel) c.LLVMCodeGenOptLevel {
|
||||||
|
return switch (self) {
|
||||||
|
.none => c.LLVMCodeGenLevelNone,
|
||||||
|
.less => c.LLVMCodeGenLevelLess,
|
||||||
|
.default => c.LLVMCodeGenLevelDefault,
|
||||||
|
.aggressive => c.LLVMCodeGenLevelAggressive,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Check if target triple indicates aarch64/arm64 (runtime check, not comptime).
|
||||||
|
pub fn isAarch64(self: TargetConfig) bool {
|
||||||
|
return self.tripleHasPrefix("aarch64", "arm64");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if target triple indicates x86_64/x86-64.
|
||||||
|
pub fn isX86_64(self: TargetConfig) bool {
|
||||||
|
return self.tripleHasPrefix("x86_64", "x86-64");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if target triple indicates Windows (contains "windows" or "win32").
|
||||||
|
pub fn isWindows(self: TargetConfig) bool {
|
||||||
|
return self.tripleContains("windows") or self.tripleContains("win32");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool {
|
||||||
|
if (self.triple) |t| {
|
||||||
|
const span = std.mem.span(t);
|
||||||
|
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
|
||||||
|
}
|
||||||
|
const dt = c.LLVMGetDefaultTargetTriple();
|
||||||
|
defer c.LLVMDisposeMessage(dt);
|
||||||
|
const span = std.mem.span(dt);
|
||||||
|
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tripleContains(self: TargetConfig, needle: []const u8) bool {
|
||||||
|
if (self.triple) |t| {
|
||||||
|
return std.mem.indexOf(u8, std.mem.span(t), needle) != null;
|
||||||
|
}
|
||||||
|
const dt = c.LLVMGetDefaultTargetTriple();
|
||||||
|
defer c.LLVMDisposeMessage(dt);
|
||||||
|
return std.mem.indexOf(u8, std.mem.span(dt), needle) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCpu(self: TargetConfig) [*:0]const u8 {
|
||||||
|
return self.cpu orelse "generic";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFeatures(self: TargetConfig) [*:0]const u8 {
|
||||||
|
return self.features orelse "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLinker(self: TargetConfig) []const u8 {
|
||||||
|
return self.linker orelse "cc";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Execute a precompiled object file in-process using LLVM's ORC JIT.
|
||||||
|
/// Takes ownership of obj_buf. Returns the exit code from main().
|
||||||
|
pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
|
||||||
|
// Create LLJIT with default builder (no custom TM needed — .o is precompiled)
|
||||||
|
var jit: c.LLVMOrcLLJITRef = null;
|
||||||
|
var err = c.LLVMOrcCreateLLJIT(&jit, null);
|
||||||
|
if (err != null) {
|
||||||
|
const msg = c.LLVMGetErrorMessage(err);
|
||||||
|
defer c.LLVMDisposeErrorMessage(msg);
|
||||||
|
std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)});
|
||||||
|
return error.CompileError;
|
||||||
|
}
|
||||||
|
defer _ = c.LLVMOrcDisposeLLJIT(jit);
|
||||||
|
|
||||||
|
// Add process symbols so JIT can find libc (printf, etc.)
|
||||||
|
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
|
||||||
|
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
|
||||||
|
var gen: c.LLVMOrcDefinitionGeneratorRef = null;
|
||||||
|
err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null);
|
||||||
|
if (err != null) {
|
||||||
|
const msg = c.LLVMGetErrorMessage(err);
|
||||||
|
defer c.LLVMDisposeErrorMessage(msg);
|
||||||
|
std.debug.print("JIT symbol gen error: {s}\n", .{std.mem.span(msg)});
|
||||||
|
return error.CompileError;
|
||||||
|
}
|
||||||
|
c.LLVMOrcJITDylibAddGenerator(jd, gen);
|
||||||
|
|
||||||
|
// Add precompiled object file (transfers ownership of obj_buf)
|
||||||
|
err = c.LLVMOrcLLJITAddObjectFile(jit, jd, obj_buf);
|
||||||
|
if (err != null) {
|
||||||
|
const msg = c.LLVMGetErrorMessage(err);
|
||||||
|
defer c.LLVMDisposeErrorMessage(msg);
|
||||||
|
std.debug.print("JIT add object error: {s}\n", .{std.mem.span(msg)});
|
||||||
|
return error.CompileError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the "main" function
|
||||||
|
var main_addr: c.LLVMOrcExecutorAddress = 0;
|
||||||
|
err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main");
|
||||||
|
if (err != null) {
|
||||||
|
const msg = c.LLVMGetErrorMessage(err);
|
||||||
|
defer c.LLVMDisposeErrorMessage(msg);
|
||||||
|
std.debug.print("JIT lookup error: {s}\n", .{std.mem.span(msg)});
|
||||||
|
return error.CompileError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to function pointer and call
|
||||||
|
const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr);
|
||||||
|
const result = main_fn();
|
||||||
|
return if (result >= 0 and result <= 255) @intCast(result) else 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, target_config: TargetConfig) !void {
|
||||||
|
var argv = std.ArrayList([]const u8).empty;
|
||||||
|
|
||||||
|
if (target_config.isWindows()) {
|
||||||
|
// Windows: MSVC-style linker flags
|
||||||
|
const linker = target_config.linker orelse "link.exe";
|
||||||
|
try argv.appendSlice(allocator, &.{ linker, output_obj });
|
||||||
|
for (extra_objects) |eo| try argv.append(allocator, eo);
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "/OUT:{s}", .{output_bin}));
|
||||||
|
|
||||||
|
for (target_config.lib_paths) |lp| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "/LIBPATH:{s}", .{lp}));
|
||||||
|
}
|
||||||
|
for (libraries) |lib| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "{s}.lib", .{lib}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unix: cc-style linker flags
|
||||||
|
try argv.appendSlice(allocator, &.{ target_config.getLinker(), output_obj, "-o", output_bin });
|
||||||
|
for (extra_objects) |eo| try argv.append(allocator, eo);
|
||||||
|
|
||||||
|
if (target_config.sysroot) |sr| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// User-supplied library paths first
|
||||||
|
for (target_config.lib_paths) |lp| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect host OS library paths when linking foreign libraries
|
||||||
|
if (libraries.len > 0 and target_config.triple == null) {
|
||||||
|
for (host_lib_paths) |path| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{path}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (libraries) |lib| {
|
||||||
|
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv_slice = try argv.toOwnedSlice(allocator);
|
||||||
|
var child = std.process.spawn(io, .{
|
||||||
|
.argv = argv_slice,
|
||||||
|
}) catch return error.LinkError;
|
||||||
|
const result = child.wait(io) catch return error.LinkError;
|
||||||
|
if (result != .exited) return error.LinkError;
|
||||||
|
if (result.exited != 0) return error.LinkError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common library paths for the host OS, computed at comptime.
|
||||||
|
pub const host_lib_paths = blk: {
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
var paths: []const []const u8 = &.{};
|
||||||
|
if (builtin.os.tag == .macos) {
|
||||||
|
if (builtin.cpu.arch == .aarch64) {
|
||||||
|
// Apple Silicon Homebrew
|
||||||
|
paths = &.{ "/opt/homebrew/lib", "/usr/local/lib" };
|
||||||
|
} else {
|
||||||
|
// Intel Mac Homebrew
|
||||||
|
paths = &.{"/usr/local/lib"};
|
||||||
|
}
|
||||||
|
} else if (builtin.os.tag == .linux) {
|
||||||
|
paths = &.{ "/usr/local/lib", "/usr/lib" };
|
||||||
|
}
|
||||||
|
break :blk paths;
|
||||||
|
};
|
||||||
@@ -410,6 +410,9 @@ opt field set: 42
|
|||||||
opt param a: 42
|
opt param a: 42
|
||||||
opt param b: 0
|
opt param b: 0
|
||||||
opt param 7: 7
|
opt param 7: 7
|
||||||
|
opt reassign: 42.500000
|
||||||
|
opt compute assign: 15.000000
|
||||||
|
opt re-null: 99.000000
|
||||||
generic opt 1: 5
|
generic opt 1: 5
|
||||||
generic opt 2: 7
|
generic opt 2: 7
|
||||||
generic opt 3: null
|
generic opt 3: null
|
||||||
|
|||||||
Reference in New Issue
Block a user