Resolves issue-0036 (LLVM verifier failure on 16-byte integer-only
struct by value through #foreign). The mismatch:
Call parameter type does not match function signature!
%load = load { i64, i64 }, ptr %alloca, align 8
[2 x i64] %call = call [2 x i64] @fn({ i64, i64 } %load)
`abiCoerceParamType` had already chosen `[2 x i64]` for 9..16-byte
non-HFA structs (the AAPCS64 / SysV AMD64 register-pair ABI slot for
that size class) on the foreign-decl side, but `coerceArg` only knew
how to bridge struct<->integer (the ≤8 B case) — not struct<->array.
LLVM's verifier rejects type-mismatched call args, so the call site
never landed.
Added the symmetric branches in coerceArg:
- Struct -> Array : alloca <array>; store <struct>; load <array>
- Array -> Struct : alloca <array>; store <array>; load <struct>
Both use the LLVM opaque-pointer memory-bitcast pattern already in
place for the integer case. They're paired with the existing
i64 <-> small-struct bridge so all four (≤8 B int, 9..16 B int,
16 B HFA, >16 B byval) ABI slots round-trip cleanly through
emit_llvm now.
File mechanics: promotes the issue-0036 repro to a focused feature
example per CLAUDE.md's issue-resolution workflow:
examples/issue-0036.sx -> examples/101-ffi-medium-struct.sx
tests/expected/issue-0036.{txt,exit} -> tests/expected/101-ffi-medium-struct.{txt,exit}
vendors/issue_0036/issue_0036.c -> vendors/ffi_medium_struct/ffi_medium_struct.c
Snapshot updated to the passing output. 89/89 regression tests pass;
chess Android build still clean.
3314 lines
166 KiB
Zig
3314 lines
166 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const llvm = @import("../llvm_api.zig");
|
|
const c = llvm.c;
|
|
const target_mod = @import("../target.zig");
|
|
const TargetConfig = target_mod.TargetConfig;
|
|
const ir_types = @import("types.zig");
|
|
const TypeId = ir_types.TypeId;
|
|
const TypeInfo = ir_types.TypeInfo;
|
|
const TypeTable = ir_types.TypeTable;
|
|
const StringId = ir_types.StringId;
|
|
const ir_inst = @import("inst.zig");
|
|
const Ref = ir_inst.Ref;
|
|
const BlockId = ir_inst.BlockId;
|
|
const FuncId = ir_inst.FuncId;
|
|
const GlobalId = ir_inst.GlobalId;
|
|
const Inst = ir_inst.Inst;
|
|
const Op = ir_inst.Op;
|
|
const Block = ir_inst.Block;
|
|
const Function = ir_inst.Function;
|
|
const Global = ir_inst.Global;
|
|
const ir_module = @import("module.zig");
|
|
const Module = ir_module.Module;
|
|
const interp_mod = @import("interp.zig");
|
|
const Interpreter = interp_mod.Interpreter;
|
|
const Value = interp_mod.Value;
|
|
|
|
// ── LLVMEmitter ─────────────────────────────────────────────────────────
|
|
// Emits LLVM IR from an IR Module. This is the Phase 3 replacement for
|
|
// the AST-based codegen.
|
|
|
|
pub const LLVMEmitter = struct {
|
|
// LLVM handles
|
|
context: c.LLVMContextRef,
|
|
llvm_module: c.LLVMModuleRef,
|
|
builder: c.LLVMBuilderRef,
|
|
target_machine: ?c.LLVMTargetMachineRef,
|
|
|
|
// IR Module being emitted
|
|
ir_mod: *const Module,
|
|
|
|
// Allocator for temporary bookkeeping
|
|
alloc: Allocator,
|
|
|
|
// Maps IR Ref → LLVM Value for the current function
|
|
ref_map: std.AutoHashMap(u32, c.LLVMValueRef),
|
|
|
|
// Maps IR FuncId → LLVM function value
|
|
func_map: std.AutoHashMap(u32, c.LLVMValueRef),
|
|
|
|
// Maps IR GlobalId → LLVM global value
|
|
global_map: std.AutoHashMap(u32, c.LLVMValueRef),
|
|
|
|
// Maps (func_idx, block_idx) → LLVM BasicBlock
|
|
block_map: std.AutoHashMap(u64, c.LLVMBasicBlockRef),
|
|
|
|
// Cached LLVM types
|
|
cached_i1: c.LLVMTypeRef,
|
|
cached_i8: c.LLVMTypeRef,
|
|
cached_i16: c.LLVMTypeRef,
|
|
cached_i32: c.LLVMTypeRef,
|
|
cached_i64: c.LLVMTypeRef,
|
|
cached_f32: c.LLVMTypeRef,
|
|
cached_f64: c.LLVMTypeRef,
|
|
cached_ptr: c.LLVMTypeRef,
|
|
cached_void: c.LLVMTypeRef,
|
|
|
|
// Current ref counter — tracks which Ref index we're emitting within a function
|
|
ref_counter: u32 = 0,
|
|
|
|
// Pending PHI nodes to fixup after all blocks in a function are emitted
|
|
pending_phis: std.ArrayList(PendingPhi),
|
|
|
|
// Whether the current function being emitted is "main" (needs i32 return for JIT)
|
|
current_func_is_main: bool = false,
|
|
current_func_idx: u32 = 0,
|
|
|
|
// Cached composite types
|
|
string_struct_type: ?c.LLVMTypeRef,
|
|
any_struct_type: ?c.LLVMTypeRef,
|
|
closure_struct_type: ?c.LLVMTypeRef,
|
|
|
|
// Cached field name arrays for reflection (TypeId → LLVM global)
|
|
field_name_arrays: std.AutoHashMap(u32, c.LLVMValueRef),
|
|
|
|
// Target configuration (stored for ABI decisions during emission)
|
|
target_config: TargetConfig,
|
|
|
|
// Build configuration accumulated from #run blocks
|
|
build_config: interp_mod.BuildConfig,
|
|
|
|
const PendingPhi = struct {
|
|
phi: c.LLVMValueRef,
|
|
block_id: BlockId, // the block this phi belongs to
|
|
param_index: u32,
|
|
};
|
|
|
|
pub fn init(alloc: Allocator, ir_mod: *const Module, module_name: [*:0]const u8, target_config: TargetConfig) LLVMEmitter {
|
|
// Initialize LLVM targets
|
|
if (target_config.triple == null) {
|
|
llvm.initNativeTarget();
|
|
} else {
|
|
llvm.initAllTargets();
|
|
}
|
|
|
|
const ctx = c.LLVMContextCreate();
|
|
const llvm_module = c.LLVMModuleCreateWithNameInContext(module_name, ctx);
|
|
const builder = c.LLVMCreateBuilderInContext(ctx);
|
|
|
|
// Set target triple
|
|
const triple_owned = target_config.triple == null;
|
|
const triple = target_config.triple orelse c.LLVMGetDefaultTargetTriple();
|
|
defer if (triple_owned) c.LLVMDisposeMessage(@constCast(triple));
|
|
|
|
c.LLVMSetTarget(llvm_module, triple);
|
|
|
|
// Create target machine and set data layout
|
|
var target: c.LLVMTargetRef = null;
|
|
var err_msg: [*c]u8 = null;
|
|
var tm: c.LLVMTargetMachineRef = null;
|
|
if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) == 0) {
|
|
tm = c.LLVMCreateTargetMachine(
|
|
target,
|
|
triple,
|
|
target_config.getCpu(),
|
|
target_config.getFeatures(),
|
|
target_config.opt_level.toLLVM(),
|
|
c.LLVMRelocPIC,
|
|
c.LLVMCodeModelDefault,
|
|
);
|
|
const dl = c.LLVMCreateTargetDataLayout(tm);
|
|
c.LLVMSetModuleDataLayout(llvm_module, dl);
|
|
c.LLVMDisposeTargetData(dl);
|
|
} else {
|
|
if (err_msg != null) c.LLVMDisposeMessage(err_msg);
|
|
}
|
|
|
|
return .{
|
|
.context = ctx,
|
|
.llvm_module = llvm_module,
|
|
.builder = builder,
|
|
.target_machine = tm,
|
|
.ir_mod = ir_mod,
|
|
.alloc = alloc,
|
|
.ref_map = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
|
.func_map = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
|
.global_map = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
|
.block_map = std.AutoHashMap(u64, c.LLVMBasicBlockRef).init(alloc),
|
|
.pending_phis = std.ArrayList(PendingPhi).empty,
|
|
.cached_i1 = c.LLVMInt1TypeInContext(ctx),
|
|
.cached_i8 = c.LLVMInt8TypeInContext(ctx),
|
|
.cached_i16 = c.LLVMInt16TypeInContext(ctx),
|
|
.cached_i32 = c.LLVMInt32TypeInContext(ctx),
|
|
.cached_i64 = c.LLVMInt64TypeInContext(ctx),
|
|
.cached_f32 = c.LLVMFloatTypeInContext(ctx),
|
|
.cached_f64 = c.LLVMDoubleTypeInContext(ctx),
|
|
.cached_ptr = c.LLVMPointerTypeInContext(ctx, 0),
|
|
.cached_void = c.LLVMVoidTypeInContext(ctx),
|
|
.string_struct_type = null,
|
|
.any_struct_type = null,
|
|
.closure_struct_type = null,
|
|
.field_name_arrays = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
|
.target_config = target_config,
|
|
.build_config = .{},
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *LLVMEmitter) void {
|
|
self.build_config.deinit(self.alloc);
|
|
self.ref_map.deinit();
|
|
self.func_map.deinit();
|
|
self.field_name_arrays.deinit();
|
|
self.global_map.deinit();
|
|
self.block_map.deinit();
|
|
if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm);
|
|
c.LLVMDisposeBuilder(self.builder);
|
|
c.LLVMDisposeModule(self.llvm_module);
|
|
c.LLVMContextDispose(self.context);
|
|
}
|
|
|
|
// ── Top-level emit ──────────────────────────────────────────────
|
|
|
|
pub fn emit(self: *LLVMEmitter) void {
|
|
// Pass 0: Declare and initialize globals
|
|
self.emitGlobals();
|
|
|
|
// Pass 0.5: Run comptime side-effect functions (#run expr; at top level)
|
|
self.runComptimeSideEffects();
|
|
|
|
// Pass 1: Declare all functions (so calls can reference them)
|
|
for (self.ir_mod.functions.items, 0..) |func, i| {
|
|
self.declareFunction(&func, @intCast(i));
|
|
}
|
|
|
|
// Pass 1.5: Initialize vtable globals (needs function declarations from Pass 1)
|
|
self.initVtableGlobals();
|
|
|
|
// Pass 2: Emit function bodies
|
|
for (self.ir_mod.functions.items, 0..) |func, i| {
|
|
if (func.is_extern or func.blocks.items.len == 0) continue;
|
|
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 {
|
|
// Skip for wasm32: 4-byte pointers vs IR's assumed 8-byte,
|
|
// so struct sizes will differ. LLVM handles emission correctly.
|
|
if (self.target_config.isWasm32()) return;
|
|
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).
|
|
/// These are functions marked `is_comptime = true` with void return that
|
|
/// aren't associated with any global. They produce compile-time output.
|
|
fn runComptimeSideEffects(self: *LLVMEmitter) void {
|
|
for (self.ir_mod.functions.items, 0..) |func, i| {
|
|
if (!func.is_comptime or func.ret != .void) continue;
|
|
// Skip functions that are global initializers (already run by emitGlobals)
|
|
var is_global_init = false;
|
|
for (self.ir_mod.globals.items) |global| {
|
|
if (global.comptime_func) |gf| {
|
|
if (@intFromEnum(gf) == i) {
|
|
is_global_init = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (is_global_init) continue;
|
|
|
|
// Run the side-effect function via interpreter
|
|
const func_id = ir_inst.FuncId.fromIndex(@intCast(i));
|
|
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
|
interp_inst.build_config = &self.build_config;
|
|
_ = interp_inst.call(func_id, &.{}) catch {};
|
|
// Write comptime output to stderr (same as old comptime VM)
|
|
if (interp_inst.output.items.len > 0) {
|
|
std.debug.print("{s}", .{interp_inst.output.items});
|
|
}
|
|
interp_inst.deinit();
|
|
}
|
|
}
|
|
|
|
fn emitGlobals(self: *LLVMEmitter) void {
|
|
for (self.ir_mod.globals.items, 0..) |global, i| {
|
|
const name = self.ir_mod.types.getString(global.name);
|
|
const llvm_ty = self.toLLVMType(global.ty);
|
|
const name_z = self.alloc.dupeZ(u8, name) catch continue;
|
|
defer self.alloc.free(name_z);
|
|
|
|
const llvm_global = c.LLVMAddGlobal(self.llvm_module, llvm_ty, name_z.ptr);
|
|
|
|
// Extern globals (`<name> : <type> #foreign;`) resolve at link time
|
|
// to a libSystem / framework symbol — no initializer, default linkage.
|
|
if (global.is_extern) {
|
|
c.LLVMSetLinkage(llvm_global, c.LLVMExternalLinkage);
|
|
self.global_map.put(@intCast(i), llvm_global) catch {};
|
|
continue;
|
|
}
|
|
|
|
c.LLVMSetLinkage(llvm_global, c.LLVMInternalLinkage);
|
|
|
|
// Evaluate comptime initializer if present
|
|
if (global.comptime_func) |func_id| {
|
|
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
|
interp_inst.build_config = &self.build_config;
|
|
const result = interp_inst.call(func_id, &.{}) catch .void_val;
|
|
const init_val = self.valueToLLVMConst(result, llvm_ty);
|
|
c.LLVMSetInitializer(llvm_global, init_val);
|
|
} else if (global.init_val) |iv| {
|
|
const init_val = switch (iv) {
|
|
.int => |v| c.LLVMConstInt(llvm_ty, @bitCast(v), 1),
|
|
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
|
.aggregate => |agg| self.emitConstAggregate(agg, llvm_ty),
|
|
.vtable => c.LLVMConstNull(llvm_ty), // placeholder — initialized in initVtableGlobals after function declarations
|
|
else => c.LLVMConstNull(llvm_ty),
|
|
};
|
|
c.LLVMSetInitializer(llvm_global, init_val);
|
|
} else {
|
|
c.LLVMSetInitializer(llvm_global, c.LLVMConstNull(llvm_ty));
|
|
}
|
|
|
|
self.global_map.put(@intCast(i), llvm_global) catch {};
|
|
}
|
|
}
|
|
|
|
/// Initialize vtable globals with function pointer constants.
|
|
/// Must run after Pass 1 (function declarations) so func_map is populated.
|
|
fn initVtableGlobals(self: *LLVMEmitter) void {
|
|
for (self.ir_mod.globals.items, 0..) |global, i| {
|
|
const iv = global.init_val orelse continue;
|
|
const func_ids = switch (iv) {
|
|
.vtable => |ids| ids,
|
|
else => continue,
|
|
};
|
|
|
|
const llvm_global = self.global_map.get(@intCast(i)) orelse continue;
|
|
const llvm_ty = self.toLLVMType(global.ty);
|
|
|
|
// Build constant struct of function pointers
|
|
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
|
defer field_vals.deinit(self.alloc);
|
|
for (func_ids) |fid| {
|
|
const llvm_func = self.func_map.get(fid.index()) orelse {
|
|
field_vals.append(self.alloc, c.LLVMConstNull(self.cached_ptr)) catch unreachable;
|
|
continue;
|
|
};
|
|
field_vals.append(self.alloc, llvm_func) catch unreachable;
|
|
}
|
|
const init_val = c.LLVMConstNamedStruct(llvm_ty, field_vals.items.ptr, @intCast(field_vals.items.len));
|
|
c.LLVMSetInitializer(llvm_global, init_val);
|
|
c.LLVMSetGlobalConstant(llvm_global, 1);
|
|
}
|
|
}
|
|
|
|
fn valueToLLVMConst(self: *LLVMEmitter, val: Value, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
|
_ = self;
|
|
return switch (val) {
|
|
.int => |v| c.LLVMConstInt(llvm_ty, @bitCast(v), 1),
|
|
.float => |v| c.LLVMConstReal(llvm_ty, v),
|
|
.boolean => |v| c.LLVMConstInt(llvm_ty, @intFromBool(v), 0),
|
|
else => c.LLVMConstNull(llvm_ty),
|
|
};
|
|
}
|
|
|
|
// ── Function declaration ────────────────────────────────────────
|
|
|
|
fn declareFunction(self: *LLVMEmitter, func: *const Function, func_idx: u32) void {
|
|
const name = self.ir_mod.types.getString(func.name);
|
|
|
|
// Skip builtins that are declared via getOrDeclare* with correct C-compatible types.
|
|
// The IR lowering creates extern stubs with IR types (e.g. memset → void return),
|
|
// but the C ABI may differ (memset returns ptr). Let getOrDeclare* handle these.
|
|
if (func.is_extern and isBuiltinLibcName(name)) {
|
|
// Still register in func_map so call resolution works
|
|
const builtin_fn = self.getOrDeclareBuiltinByName(name);
|
|
if (builtin_fn) |bf| {
|
|
self.func_map.put(func_idx, bf) catch unreachable;
|
|
return;
|
|
}
|
|
}
|
|
|
|
const is_main = std.mem.eql(u8, name, "main");
|
|
|
|
// main always returns i32 at the LLVM level (JIT expects it)
|
|
const raw_ret_ty = self.toLLVMType(func.ret);
|
|
const needs_c_abi = func.is_extern or func.call_conv == .c;
|
|
const ret_ty = if (is_main) self.cached_i32 else if (needs_c_abi) self.abiCoerceParamType(func.ret, raw_ret_ty) else raw_ret_ty;
|
|
|
|
// Build parameter types — apply C ABI coercion for foreign/callconv(.c) functions
|
|
const param_count: c_uint = @intCast(func.params.len);
|
|
const param_types = self.alloc.alloc(c.LLVMTypeRef, func.params.len) catch unreachable;
|
|
defer self.alloc.free(param_types);
|
|
for (func.params, 0..) |param, j| {
|
|
const llvm_ty = self.toLLVMType(param.ty);
|
|
param_types[j] = if (needs_c_abi) self.abiCoerceParamType(param.ty, llvm_ty) else llvm_ty;
|
|
}
|
|
|
|
const fn_type = c.LLVMFunctionType(ret_ty, param_types.ptr, param_count, 0);
|
|
const name_z = self.alloc.dupeZ(u8, name) catch unreachable;
|
|
defer self.alloc.free(name_z);
|
|
|
|
const llvm_func = c.LLVMAddFunction(self.llvm_module, name_z.ptr, fn_type);
|
|
|
|
// Set linkage
|
|
switch (func.linkage) {
|
|
.external => c.LLVMSetLinkage(llvm_func, c.LLVMExternalLinkage),
|
|
.internal => c.LLVMSetLinkage(llvm_func, c.LLVMInternalLinkage),
|
|
.private => c.LLVMSetLinkage(llvm_func, c.LLVMPrivateLinkage),
|
|
}
|
|
|
|
// Set calling convention
|
|
if (func.call_conv == .c) {
|
|
c.LLVMSetFunctionCallConv(llvm_func, c.LLVMCCallConv);
|
|
}
|
|
|
|
// Add frame-pointer and nounwind attributes for correct ARM64 codegen
|
|
{
|
|
const fp_kind = "frame-pointer";
|
|
const fp_val = "all";
|
|
const fp_attr = c.LLVMCreateStringAttribute(
|
|
self.context,
|
|
fp_kind.ptr,
|
|
@intCast(fp_kind.len),
|
|
fp_val.ptr,
|
|
@intCast(fp_val.len),
|
|
);
|
|
const func_idx_attr: c.LLVMAttributeIndex = @bitCast(@as(i32, -1));
|
|
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, fp_attr);
|
|
|
|
// Add nounwind
|
|
const nounwind_id = c.LLVMGetEnumAttributeKindForName("nounwind", 8);
|
|
const nounwind_attr = c.LLVMCreateEnumAttribute(self.context, nounwind_id, 0);
|
|
c.LLVMAddAttributeAtIndex(llvm_func, func_idx_attr, nounwind_attr);
|
|
}
|
|
|
|
// Apple ARM64 ABI for >16B non-HFA composites: pass by reference
|
|
// via a pointer in the next int register (NOT via LLVM's `byval`
|
|
// attribute, which lowers the struct on the stack — incompatible
|
|
// with what `clang` emits and what foreign C callees expect).
|
|
// abiCoerceParamType returned `ptr` for these slots, so the formal
|
|
// param IS a plain pointer; the prologue loads the struct back.
|
|
|
|
self.func_map.put(func_idx, llvm_func) catch unreachable;
|
|
}
|
|
|
|
// ── Function body emission ──────────────────────────────────────
|
|
|
|
fn emitFunction(self: *LLVMEmitter, func: *const Function, func_idx: u32) void {
|
|
const llvm_func = self.func_map.get(func_idx) orelse unreachable;
|
|
const name = self.ir_mod.types.getString(func.name);
|
|
self.current_func_is_main = std.mem.eql(u8, name, "main");
|
|
self.current_func_idx = func_idx;
|
|
|
|
// Clear ref_map and pre-map parameter refs
|
|
self.ref_map.clearRetainingCapacity();
|
|
self.ref_counter = 0;
|
|
|
|
// Refs 0..N-1 are function parameters (matching the IR convention)
|
|
for (0..func.params.len) |pi| {
|
|
const param_val = c.LLVMGetParam(llvm_func, @intCast(pi));
|
|
self.mapRef(param_val);
|
|
}
|
|
|
|
// Create all basic blocks first (so branches can reference them)
|
|
for (func.blocks.items, 0..) |block, bi| {
|
|
const block_name = self.ir_mod.types.getString(block.name);
|
|
const block_name_z = self.alloc.dupeZ(u8, block_name) catch unreachable;
|
|
defer self.alloc.free(block_name_z);
|
|
const bb = c.LLVMAppendBasicBlockInContext(self.context, llvm_func, block_name_z.ptr);
|
|
const block_key = makeBlockKey(func_idx, @intCast(bi));
|
|
self.block_map.put(block_key, bb) catch unreachable;
|
|
}
|
|
|
|
// byval params arrive as `ptr` in LLVM but the IR body expects struct values.
|
|
// At entry, load each byval param into a struct SSA value and re-map its ref.
|
|
const needs_c_abi = func.is_extern or func.call_conv == .c;
|
|
if (needs_c_abi and func.blocks.items.len > 0) {
|
|
const entry_key = makeBlockKey(func_idx, 0);
|
|
const entry_bb = self.block_map.get(entry_key) orelse unreachable;
|
|
c.LLVMPositionBuilderAtEnd(self.builder, entry_bb);
|
|
for (func.params, 0..) |param, pi| {
|
|
const raw_llvm_ty = self.toLLVMType(param.ty);
|
|
if (self.needsByval(param.ty, raw_llvm_ty)) {
|
|
const ptr_val = c.LLVMGetParam(llvm_func, @intCast(pi));
|
|
const loaded = c.LLVMBuildLoad2(self.builder, raw_llvm_ty, ptr_val, "byval.load");
|
|
self.ref_map.put(@intCast(pi), loaded) catch unreachable;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear pending phis for this function
|
|
self.pending_phis.clearRetainingCapacity();
|
|
|
|
// Emit instructions for each block — use first_ref to sync ref numbering
|
|
for (func.blocks.items, 0..) |block, bi| {
|
|
const block_key = makeBlockKey(func_idx, @intCast(bi));
|
|
const bb = self.block_map.get(block_key) orelse unreachable;
|
|
c.LLVMPositionBuilderAtEnd(self.builder, bb);
|
|
|
|
// Reset ref_counter to this block's actual starting ref
|
|
// (blocks may not be in emission order due to nested control flow)
|
|
self.ref_counter = block.first_ref;
|
|
|
|
for (block.insts.items, 0..) |instruction, inst_i| {
|
|
_ = inst_i;
|
|
self.emitInst(&instruction, func_idx);
|
|
}
|
|
}
|
|
|
|
// Fixup PHI nodes: scan all blocks for branches that pass args
|
|
self.fixupPhiNodes(func, func_idx);
|
|
}
|
|
|
|
/// After emitting all blocks, fill in PHI incoming values from branch args.
|
|
fn fixupPhiNodes(self: *LLVMEmitter, func: *const Function, func_idx: u32) void {
|
|
if (self.pending_phis.items.len == 0) return;
|
|
|
|
for (func.blocks.items, 0..) |block, bi| {
|
|
const src_key = makeBlockKey(func_idx, @intCast(bi));
|
|
const src_bb = self.block_map.get(src_key) orelse continue;
|
|
|
|
for (block.insts.items) |instruction| {
|
|
switch (instruction.op) {
|
|
.br => |branch| {
|
|
self.addPhiIncoming(branch.target, branch.args, src_bb);
|
|
},
|
|
.cond_br => |cb| {
|
|
self.addPhiIncoming(cb.then_target, cb.then_args, src_bb);
|
|
self.addPhiIncoming(cb.else_target, cb.else_args, src_bb);
|
|
},
|
|
.switch_br => |sw| {
|
|
for (sw.cases) |case| {
|
|
self.addPhiIncoming(case.target, case.args, src_bb);
|
|
}
|
|
self.addPhiIncoming(sw.default, sw.default_args, src_bb);
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn addPhiIncoming(self: *LLVMEmitter, target: BlockId, args: []const Ref, src_bb: c.LLVMBasicBlockRef) void {
|
|
for (args, 0..) |arg, pi| {
|
|
const val = self.resolveRef(arg) orelse continue;
|
|
// Find the matching pending phi
|
|
for (self.pending_phis.items) |pp| {
|
|
if (pp.block_id.index() == target.index() and pp.param_index == pi) {
|
|
var incoming_vals = [1]c.LLVMValueRef{val};
|
|
var incoming_bbs = [1]c.LLVMBasicBlockRef{src_bb};
|
|
c.LLVMAddIncoming(pp.phi, &incoming_vals, &incoming_bbs, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Instruction emission ────────────────────────────────────────
|
|
|
|
fn emitInst(self: *LLVMEmitter, instruction: *const Inst, func_idx: u32) void {
|
|
switch (instruction.op) {
|
|
// ── Constants ───────────────────────────────────────────
|
|
.const_int => |val| {
|
|
const ty = self.toLLVMType(instruction.ty);
|
|
const kind = c.LLVMGetTypeKind(ty);
|
|
const llvm_val = if (kind == c.LLVMIntegerTypeKind)
|
|
c.LLVMConstInt(ty, @bitCast(val), 1)
|
|
else if (kind == c.LLVMPointerTypeKind)
|
|
c.LLVMConstNull(ty)
|
|
else
|
|
// void or other non-integer type: emit i64 0 as unused placeholder
|
|
c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0);
|
|
self.mapRef(llvm_val);
|
|
},
|
|
.const_float => |val| {
|
|
const ty = self.toLLVMType(instruction.ty);
|
|
const llvm_val = c.LLVMConstReal(ty, val);
|
|
self.mapRef(llvm_val);
|
|
},
|
|
.const_bool => |val| {
|
|
const llvm_val = c.LLVMConstInt(self.cached_i1, @intFromBool(val), 0);
|
|
self.mapRef(llvm_val);
|
|
},
|
|
.const_string => |str_id| {
|
|
const str = self.ir_mod.types.getString(str_id);
|
|
const llvm_val = self.emitStringConstant(str);
|
|
self.mapRef(llvm_val);
|
|
},
|
|
.const_null => {
|
|
const ty = if (instruction.ty == .void) self.cached_ptr else self.toLLVMType(instruction.ty);
|
|
const llvm_val = c.LLVMConstNull(ty);
|
|
self.mapRef(llvm_val);
|
|
},
|
|
.const_undef => {
|
|
if (instruction.ty == .void) {
|
|
// void has no value — map to undef i64 as placeholder
|
|
self.mapRef(c.LLVMGetUndef(self.cached_i64));
|
|
} else {
|
|
const ty = self.toLLVMType(instruction.ty);
|
|
const llvm_val = c.LLVMGetUndef(ty);
|
|
self.mapRef(llvm_val);
|
|
}
|
|
},
|
|
|
|
// ── Arithmetic ─────────────────────────────────────────
|
|
.add => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFAdd(self.builder, lhs, rhs, "fadd")
|
|
else
|
|
c.LLVMBuildAdd(self.builder, lhs, rhs, "add");
|
|
self.mapRef(result);
|
|
},
|
|
.sub => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFSub(self.builder, lhs, rhs, "fsub")
|
|
else
|
|
c.LLVMBuildSub(self.builder, lhs, rhs, "sub");
|
|
self.mapRef(result);
|
|
},
|
|
.mul => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFMul(self.builder, lhs, rhs, "fmul")
|
|
else
|
|
c.LLVMBuildMul(self.builder, lhs, rhs, "mul");
|
|
self.mapRef(result);
|
|
},
|
|
.div => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFDiv(self.builder, lhs, rhs, "fdiv")
|
|
else if (isSignedType(instruction.ty))
|
|
c.LLVMBuildSDiv(self.builder, lhs, rhs, "sdiv")
|
|
else
|
|
c.LLVMBuildUDiv(self.builder, lhs, rhs, "udiv");
|
|
self.mapRef(result);
|
|
},
|
|
.mod => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFRem(self.builder, lhs, rhs, "fmod")
|
|
else if (isSignedType(instruction.ty))
|
|
c.LLVMBuildSRem(self.builder, lhs, rhs, "srem")
|
|
else
|
|
c.LLVMBuildURem(self.builder, lhs, rhs, "urem");
|
|
self.mapRef(result);
|
|
},
|
|
.neg => |un| {
|
|
const operand = self.resolveRef(un.operand);
|
|
const is_float = isFloatOrVecFloat(instruction.ty, &self.ir_mod.types);
|
|
const result = if (is_float)
|
|
c.LLVMBuildFNeg(self.builder, operand, "fneg")
|
|
else
|
|
c.LLVMBuildNeg(self.builder, operand, "neg");
|
|
self.mapRef(result);
|
|
},
|
|
|
|
// ── Bitwise ────────────────────────────────────────────
|
|
.bit_and => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "and"));
|
|
},
|
|
.bit_or => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "or"));
|
|
},
|
|
.bit_xor => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
self.mapRef(c.LLVMBuildXor(self.builder, lhs, rhs, "xor"));
|
|
},
|
|
.bit_not => |un| {
|
|
const operand = self.resolveRef(un.operand);
|
|
self.mapRef(c.LLVMBuildNot(self.builder, operand, "not"));
|
|
},
|
|
.shl => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
self.mapRef(c.LLVMBuildShl(self.builder, lhs, rhs, "shl"));
|
|
},
|
|
.shr => |bin| {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
self.matchBinOpTypes(&lhs, &rhs, instruction.ty);
|
|
// Use arithmetic shift right for signed, logical for unsigned
|
|
const result = if (isSignedType(instruction.ty))
|
|
c.LLVMBuildAShr(self.builder, lhs, rhs, "ashr")
|
|
else
|
|
c.LLVMBuildLShr(self.builder, lhs, rhs, "lshr");
|
|
self.mapRef(result);
|
|
},
|
|
|
|
// ── Comparisons ───────────────────────────────────────
|
|
.cmp_eq => |bin| self.emitCmp(bin, instruction.ty, c.LLVMIntEQ, c.LLVMRealOEQ),
|
|
.cmp_ne => |bin| self.emitCmp(bin, instruction.ty, c.LLVMIntNE, c.LLVMRealONE),
|
|
.cmp_lt => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLT, c.LLVMIntULT, c.LLVMRealOLT),
|
|
.cmp_le => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLE, c.LLVMIntULE, c.LLVMRealOLE),
|
|
.cmp_gt => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGT, c.LLVMIntUGT, c.LLVMRealOGT),
|
|
.cmp_ge => |bin| self.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGE, c.LLVMIntUGE, c.LLVMRealOGE),
|
|
.str_eq => |bin| self.emitStrCmp(bin, true),
|
|
.str_ne => |bin| self.emitStrCmp(bin, false),
|
|
|
|
// ── Logical ───────────────────────────────────────────
|
|
.bool_and => |bin| {
|
|
const lhs = self.resolveRef(bin.lhs);
|
|
const rhs = self.resolveRef(bin.rhs);
|
|
self.mapRef(c.LLVMBuildAnd(self.builder, lhs, rhs, "land"));
|
|
},
|
|
.bool_or => |bin| {
|
|
const lhs = self.resolveRef(bin.lhs);
|
|
const rhs = self.resolveRef(bin.rhs);
|
|
self.mapRef(c.LLVMBuildOr(self.builder, lhs, rhs, "lor"));
|
|
},
|
|
.bool_not => |un| {
|
|
const operand = self.resolveRef(un.operand);
|
|
self.mapRef(c.LLVMBuildNot(self.builder, operand, "lnot"));
|
|
},
|
|
|
|
// ── Memory ────────────────────────────────────────────
|
|
.alloca => |elem_ty| {
|
|
const llvm_ty = self.toLLVMType(elem_ty);
|
|
const result = c.LLVMBuildAlloca(self.builder, llvm_ty, "alloca");
|
|
self.mapRef(result);
|
|
},
|
|
.load => |un| {
|
|
const ptr = self.resolveRef(un.operand);
|
|
const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr));
|
|
if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) {
|
|
const llvm_ty = self.toLLVMType(instruction.ty);
|
|
const result = c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "load");
|
|
self.mapRef(result);
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(if (instruction.ty == .void) .s64 else instruction.ty)));
|
|
}
|
|
},
|
|
.store => |st| {
|
|
const ptr = self.resolveRef(st.ptr);
|
|
var val = self.resolveRef(st.val);
|
|
// Guard: don't store void types or store to non-pointer
|
|
const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr));
|
|
const val_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(val));
|
|
if (ptr_kind == c.LLVMPointerTypeKind and val_kind != c.LLVMVoidTypeKind) {
|
|
// Coerce value to match the IR-declared pointer target type.
|
|
// E.g. storing i64 to *i8 (from index_gep on string) needs truncation.
|
|
//
|
|
// Only unwrap .pointer (from index_gep/alloca: *element → element).
|
|
// Never unwrap .many_pointer — it only appears as struct_gep field
|
|
// value types (e.g., [*]BigNode), where unwrapping to the element
|
|
// type gives a wrong store size (stores BigNode-sized instead of ptr).
|
|
if (self.getRefIRType(st.ptr)) |ptr_ir_ty| {
|
|
const pointee_info = self.ir_mod.types.get(ptr_ir_ty);
|
|
const target_ty: ?c.LLVMTypeRef = switch (pointee_info) {
|
|
.pointer => |p| self.toLLVMType(p.pointee),
|
|
else => null,
|
|
};
|
|
if (target_ty) |tt| {
|
|
val = self.coerceArg(val, tt);
|
|
}
|
|
}
|
|
_ = c.LLVMBuildStore(self.builder, val, ptr);
|
|
}
|
|
self.advanceRefCounter();
|
|
},
|
|
.heap_alloc => |un| {
|
|
// malloc(size) → *void
|
|
const size = self.coerceArg(self.resolveRef(un.operand), self.sizeType());
|
|
const malloc_fn = self.getOrDeclareMalloc();
|
|
var args = [_]c.LLVMValueRef{size};
|
|
const result = c.LLVMBuildCall2(
|
|
self.builder,
|
|
self.getMallocType(),
|
|
malloc_fn,
|
|
&args,
|
|
1,
|
|
"heap",
|
|
);
|
|
self.mapRef(result);
|
|
},
|
|
.heap_free => |un| {
|
|
// free(ptr)
|
|
const ptr = self.resolveRef(un.operand);
|
|
const free_fn = self.getOrDeclareFree();
|
|
var args = [_]c.LLVMValueRef{ptr};
|
|
_ = c.LLVMBuildCall2(
|
|
self.builder,
|
|
self.getFreeType(),
|
|
free_fn,
|
|
&args,
|
|
1,
|
|
"",
|
|
);
|
|
self.advanceRefCounter();
|
|
},
|
|
|
|
// ── Globals ───────────────────────────────────────────
|
|
.global_get => |gid| {
|
|
const llvm_global = self.global_map.get(gid.index()) orelse {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
return;
|
|
};
|
|
const llvm_ty = self.toLLVMType(instruction.ty);
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, llvm_ty, llvm_global, "gload"));
|
|
},
|
|
.global_addr => |gid| {
|
|
const llvm_global = self.global_map.get(gid.index()) orelse {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
return;
|
|
};
|
|
// Return the global's address directly (no load)
|
|
self.mapRef(llvm_global);
|
|
},
|
|
.func_ref => |fid| {
|
|
// Produce a reference to the function as a function pointer value
|
|
if (self.func_map.get(@intFromEnum(fid))) |llvm_func| {
|
|
self.mapRef(llvm_func);
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
},
|
|
.global_set => |gs| {
|
|
const llvm_global = self.global_map.get(gs.global.index()) orelse {
|
|
self.advanceRefCounter();
|
|
return;
|
|
};
|
|
const val = self.resolveRef(gs.value);
|
|
_ = c.LLVMBuildStore(self.builder, val, llvm_global);
|
|
self.advanceRefCounter();
|
|
},
|
|
|
|
// ── Conversions ───────────────────────────────────────
|
|
.widen => |conv| {
|
|
const operand = self.resolveRef(conv.operand);
|
|
const to_ty = self.toLLVMType(conv.to);
|
|
const result = self.emitConversion(operand, conv.from, conv.to, to_ty);
|
|
self.mapRef(result);
|
|
},
|
|
.narrow => |conv| {
|
|
const operand = self.resolveRef(conv.operand);
|
|
const to_ty = self.toLLVMType(conv.to);
|
|
const result = self.emitConversion(operand, conv.from, conv.to, to_ty);
|
|
self.mapRef(result);
|
|
},
|
|
.bitcast => |conv| {
|
|
const operand = self.resolveRef(conv.operand);
|
|
const to_ty = self.toLLVMType(conv.to);
|
|
self.mapRef(c.LLVMBuildBitCast(self.builder, operand, to_ty, "bitcast"));
|
|
},
|
|
.int_to_float => |conv| {
|
|
const operand = self.resolveRef(conv.operand);
|
|
const to_ty = self.toLLVMType(conv.to);
|
|
const result = if (isSignedType(conv.from))
|
|
c.LLVMBuildSIToFP(self.builder, operand, to_ty, "sitofp")
|
|
else
|
|
c.LLVMBuildUIToFP(self.builder, operand, to_ty, "uitofp");
|
|
self.mapRef(result);
|
|
},
|
|
.float_to_int => |conv| {
|
|
const operand = self.resolveRef(conv.operand);
|
|
const to_ty = self.toLLVMType(conv.to);
|
|
const result = if (isSignedType(conv.to))
|
|
c.LLVMBuildFPToSI(self.builder, operand, to_ty, "fptosi")
|
|
else
|
|
c.LLVMBuildFPToUI(self.builder, operand, to_ty, "fptoui");
|
|
self.mapRef(result);
|
|
},
|
|
|
|
// ── Pointer ops ───────────────────────────────────────
|
|
.addr_of => |un| {
|
|
// addr_of returns the pointer directly (the operand is already a ptr from alloca)
|
|
self.mapRef(self.resolveRef(un.operand));
|
|
},
|
|
.deref => |un| {
|
|
const ptr = self.resolveRef(un.operand);
|
|
const llvm_ty = self.toLLVMType(instruction.ty);
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "deref"));
|
|
},
|
|
|
|
// ── Calls ─────────────────────────────────────────────
|
|
.call => |call_op| {
|
|
// Evaluate comptime functions at compile time
|
|
const callee_func = &self.ir_mod.functions.items[call_op.callee.index()];
|
|
if (callee_func.is_comptime and call_op.args.len == 0) {
|
|
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
|
interp_inst.build_config = &self.build_config;
|
|
defer interp_inst.deinit();
|
|
if (interp_inst.call(call_op.callee, &.{})) |result| {
|
|
if (result.asInt()) |v| {
|
|
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0));
|
|
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 |_| {}
|
|
}
|
|
const callee = self.func_map.get(call_op.callee.index()) orelse {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
return;
|
|
};
|
|
const arg_count: c_uint = @intCast(call_op.args.len);
|
|
const args = self.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable;
|
|
defer self.alloc.free(args);
|
|
for (call_op.args, 0..) |arg_ref, j| {
|
|
args[j] = self.resolveRef(arg_ref);
|
|
}
|
|
// Get the function type from LLVM and coerce arguments
|
|
const fn_ty = c.LLVMGlobalGetValueType(callee);
|
|
const param_count = c.LLVMCountParamTypes(fn_ty);
|
|
const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c;
|
|
if (param_count > 0) {
|
|
const param_types = self.alloc.alloc(c.LLVMTypeRef, param_count) catch unreachable;
|
|
defer self.alloc.free(param_types);
|
|
c.LLVMGetParamTypes(fn_ty, param_types.ptr);
|
|
for (0..@min(args.len, param_count)) |j| {
|
|
// Materialize byval args before coercion so we pass a ptr instead of the struct value.
|
|
if (callee_needs_c_abi and j < callee_func.params.len) {
|
|
const ir_ty = callee_func.params[j].ty;
|
|
const raw_struct = self.toLLVMType(ir_ty);
|
|
if (self.needsByval(ir_ty, raw_struct)) {
|
|
args[j] = self.materializeByvalArg(args[j], raw_struct);
|
|
continue;
|
|
}
|
|
}
|
|
args[j] = self.coerceArg(args[j], param_types[j]);
|
|
}
|
|
}
|
|
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);
|
|
},
|
|
.call_indirect => |call_op| {
|
|
const callee = self.resolveRef(call_op.callee);
|
|
const arg_count: c_uint = @intCast(call_op.args.len);
|
|
const args = self.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable;
|
|
defer self.alloc.free(args);
|
|
for (call_op.args, 0..) |arg_ref, j| {
|
|
args[j] = self.resolveRef(arg_ref);
|
|
}
|
|
|
|
// Get callee's IR type to resolve parameter types accurately
|
|
const callee_ir_ty = self.getRefIRType(call_op.callee);
|
|
const fn_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: {
|
|
if (!cty.isBuiltin()) {
|
|
const ci = self.ir_mod.types.get(cty);
|
|
switch (ci) {
|
|
.function => |f| break :blk f.params,
|
|
.closure => |cl| break :blk cl.params,
|
|
else => {},
|
|
}
|
|
}
|
|
break :blk null;
|
|
} else null;
|
|
|
|
// Read the fn-pointer type's calling convention. Only `.c` opts
|
|
// into the C-ABI byval coercion for >16B aggregate params.
|
|
const fp_is_c_abi: bool = if (callee_ir_ty) |cty| blk: {
|
|
if (!cty.isBuiltin()) {
|
|
const ci = self.ir_mod.types.get(cty);
|
|
if (ci == .function and ci.function.call_conv == .c) break :blk true;
|
|
}
|
|
break :blk false;
|
|
} else false;
|
|
|
|
const ret_ty = if (callee_ir_ty) |cty| blk: {
|
|
if (!cty.isBuiltin()) {
|
|
const ci = self.ir_mod.types.get(cty);
|
|
switch (ci) {
|
|
.function => |f| break :blk self.toLLVMType(f.ret),
|
|
.closure => |cl| break :blk self.toLLVMType(cl.ret),
|
|
else => {},
|
|
}
|
|
}
|
|
break :blk self.toLLVMType(instruction.ty);
|
|
} else self.toLLVMType(instruction.ty);
|
|
|
|
const param_tys = self.alloc.alloc(c.LLVMTypeRef, call_op.args.len) catch unreachable;
|
|
defer self.alloc.free(param_tys);
|
|
if (fn_params) |fp| {
|
|
for (0..call_op.args.len) |j| {
|
|
if (j < fp.len) {
|
|
const raw_struct = self.toLLVMType(fp[j]);
|
|
if (fp_is_c_abi and self.needsByval(fp[j], raw_struct)) {
|
|
args[j] = self.materializeByvalArg(args[j], raw_struct);
|
|
param_tys[j] = self.cached_ptr;
|
|
continue;
|
|
}
|
|
var llvm_pty = raw_struct;
|
|
// 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;
|
|
args[j] = self.coerceArg(args[j], llvm_pty);
|
|
} else {
|
|
param_tys[j] = c.LLVMTypeOf(args[j]);
|
|
}
|
|
}
|
|
} else {
|
|
for (args, 0..) |arg, j| {
|
|
param_tys[j] = c.LLVMTypeOf(arg);
|
|
}
|
|
}
|
|
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, arg_count, 0);
|
|
var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (instruction.ty == .void) "" else "icall");
|
|
|
|
// Coerce call result to instruction's expected type
|
|
const expected_ty = self.toLLVMType(instruction.ty);
|
|
if (instruction.ty != .void and c.LLVMTypeOf(result) != expected_ty) {
|
|
result = self.coerceArg(result, expected_ty);
|
|
}
|
|
self.mapRef(result);
|
|
},
|
|
|
|
// ── Terminators ────────────────────────────────────────
|
|
.ret => |un| {
|
|
var val = self.resolveRef(un.operand);
|
|
// Coerce return value to match the function's LLVM return type
|
|
const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
|
|
const fn_ty = c.LLVMGlobalGetValueType(llvm_func);
|
|
const expected_ret = c.LLVMGetReturnType(fn_ty);
|
|
val = self.coerceArg(val, expected_ret);
|
|
// If coercion didn't fix the type (e.g. dead comptime function),
|
|
// emit undef of the correct type to avoid LLVM verification error
|
|
if (c.LLVMTypeOf(val) != expected_ret) {
|
|
val = c.LLVMGetUndef(expected_ret);
|
|
}
|
|
_ = c.LLVMBuildRet(self.builder, val);
|
|
self.advanceRefCounter();
|
|
},
|
|
.ret_void => {
|
|
if (self.current_func_is_main) {
|
|
// main must return i32 0 for JIT
|
|
_ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(self.cached_i32, 0, 0));
|
|
} else {
|
|
_ = c.LLVMBuildRetVoid(self.builder);
|
|
}
|
|
self.advanceRefCounter();
|
|
},
|
|
.@"unreachable" => {
|
|
_ = c.LLVMBuildUnreachable(self.builder);
|
|
self.advanceRefCounter();
|
|
},
|
|
.br => |branch| {
|
|
const target = self.getBlock(func_idx, branch.target);
|
|
_ = c.LLVMBuildBr(self.builder, target);
|
|
self.advanceRefCounter();
|
|
},
|
|
.cond_br => |cbr| {
|
|
var cond = self.resolveRef(cbr.cond);
|
|
const then_bb = self.getBlock(func_idx, cbr.then_target);
|
|
const else_bb = self.getBlock(func_idx, cbr.else_target);
|
|
// Coerce condition to i1 if needed (e.g., loaded bool stored as i64)
|
|
const cond_ty = c.LLVMTypeOf(cond);
|
|
const cond_kind = c.LLVMGetTypeKind(cond_ty);
|
|
if (cond_ty != self.cached_i1) {
|
|
if (cond_kind == c.LLVMPointerTypeKind) {
|
|
cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool");
|
|
} else if (cond_kind == c.LLVMIntegerTypeKind) {
|
|
cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstInt(cond_ty, 0, 0), "tobool");
|
|
} else if (cond_kind == c.LLVMStructTypeKind) {
|
|
// Struct values are always truthy
|
|
cond = c.LLVMConstInt(self.cached_i1, 1, 0);
|
|
} else {
|
|
cond = c.LLVMConstInt(self.cached_i1, 1, 0); // default truthy
|
|
}
|
|
}
|
|
_ = c.LLVMBuildCondBr(self.builder, cond, then_bb, else_bb);
|
|
self.advanceRefCounter();
|
|
},
|
|
|
|
// ── Struct ops ────────────────────────────────────────────
|
|
.struct_init => |agg| {
|
|
const struct_ty = self.toLLVMType(instruction.ty);
|
|
const type_kind = c.LLVMGetTypeKind(struct_ty);
|
|
// For vector types, use InsertElement instead of InsertValue
|
|
const is_vector = type_kind == c.LLVMVectorTypeKind or type_kind == c.LLVMScalableVectorTypeKind;
|
|
// For array types, get expected element type for coercion
|
|
const is_array = type_kind == c.LLVMArrayTypeKind;
|
|
const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null;
|
|
var result = c.LLVMGetUndef(struct_ty);
|
|
for (agg.fields, 0..) |field_ref, i| {
|
|
var field_val = self.resolveRef(field_ref);
|
|
if (is_vector) {
|
|
// Coerce element to match vector element type
|
|
const vec_elem_ty = c.LLVMGetElementType(struct_ty);
|
|
const val_ty = c.LLVMTypeOf(field_val);
|
|
if (val_ty != vec_elem_ty) {
|
|
field_val = self.coerceArg(field_val, vec_elem_ty);
|
|
}
|
|
const idx = c.LLVMConstInt(self.cached_i32, @intCast(i), 0);
|
|
result = c.LLVMBuildInsertElement(self.builder, result, field_val, idx, "vi");
|
|
} else {
|
|
// Coerce element to match array element type if needed
|
|
if (elem_llvm_ty) |elt| {
|
|
const val_ty = c.LLVMTypeOf(field_val);
|
|
if (val_ty != elt) {
|
|
const val_kind = c.LLVMGetTypeKind(val_ty);
|
|
const elt_kind = c.LLVMGetTypeKind(elt);
|
|
if (val_kind == c.LLVMIntegerTypeKind and elt_kind == c.LLVMIntegerTypeKind) {
|
|
const val_w = c.LLVMGetIntTypeWidth(val_ty);
|
|
const elt_w = c.LLVMGetIntTypeWidth(elt);
|
|
if (val_w > elt_w) {
|
|
field_val = c.LLVMBuildTrunc(self.builder, field_val, elt, "atrunc");
|
|
} else if (val_w < elt_w) {
|
|
field_val = c.LLVMBuildZExt(self.builder, field_val, elt, "aext");
|
|
}
|
|
}
|
|
}
|
|
} else if (type_kind == c.LLVMStructTypeKind) {
|
|
// Coerce struct field value to match declared field type
|
|
const n_elts = c.LLVMCountStructElementTypes(struct_ty);
|
|
if (n_elts > 0 and i < n_elts) {
|
|
const field_ty = c.LLVMStructGetTypeAtIndex(struct_ty, @intCast(i));
|
|
const val_ty = c.LLVMTypeOf(field_val);
|
|
if (val_ty != field_ty) {
|
|
field_val = self.coerceArg(field_val, field_ty);
|
|
}
|
|
}
|
|
}
|
|
result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "si");
|
|
}
|
|
}
|
|
self.mapRef(result);
|
|
},
|
|
.struct_get => |fa| {
|
|
const base = self.resolveRef(fa.base);
|
|
// Safety: null base means unresolved reference — emit undef
|
|
if (base == null) {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
} else {
|
|
// Safety: check that base is an aggregate type (struct/array/vector), not scalar
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const base_ty_kind = c.LLVMGetTypeKind(base_ty);
|
|
if (base_ty_kind == c.LLVMVectorTypeKind or base_ty_kind == c.LLVMScalableVectorTypeKind) {
|
|
// Vector: use ExtractElement with an index
|
|
const idx = c.LLVMConstInt(self.cached_i32, @intCast(fa.field_index), 0);
|
|
const result = c.LLVMBuildExtractElement(self.builder, base, idx, "ve");
|
|
self.mapRef(result);
|
|
} else if (base_ty_kind == c.LLVMStructTypeKind or base_ty_kind == c.LLVMArrayTypeKind) {
|
|
// Validate field index is in bounds
|
|
const n_fields = if (base_ty_kind == c.LLVMStructTypeKind) c.LLVMCountStructElementTypes(base_ty) else 0;
|
|
// Check builder has valid insert point
|
|
const insert_bb = c.LLVMGetInsertBlock(self.builder);
|
|
if (insert_bb == null or (n_fields == 0 and base_ty_kind == c.LLVMStructTypeKind) or (n_fields > 0 and fa.field_index >= n_fields)) {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
} else {
|
|
const result = c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "sg");
|
|
self.mapRef(result);
|
|
}
|
|
} else {
|
|
// Base is not an aggregate (e.g., placeholder undef of scalar type)
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
}
|
|
}
|
|
},
|
|
.struct_gep => |fa| {
|
|
const base_ptr = self.resolveRef(fa.base);
|
|
// Safety: verify base is a pointer before GEP
|
|
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
|
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
|
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);
|
|
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");
|
|
self.mapRef(result);
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
},
|
|
|
|
// ── Enum ops ─────────────────────────────────────────────
|
|
.enum_init => |ei| {
|
|
if (ei.payload.isNone()) {
|
|
// Simple enum (no payload) — just a tag integer
|
|
const ty = self.toLLVMType(instruction.ty);
|
|
const ty_kind = c.LLVMGetTypeKind(ty);
|
|
if (ty_kind == c.LLVMIntegerTypeKind) {
|
|
// Plain enum or builtin integer → integer constant
|
|
self.mapRef(c.LLVMConstInt(ty, ei.tag, 0));
|
|
} else if (ty_kind == c.LLVMStructTypeKind) {
|
|
// Tagged union with no payload — header field 0 holds the tag
|
|
const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0);
|
|
const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0);
|
|
var result = c.LLVMGetUndef(ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag");
|
|
self.mapRef(result);
|
|
} else {
|
|
self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0));
|
|
}
|
|
} else {
|
|
// Tagged union with payload — { header, payload_bytes }
|
|
const union_ty = self.toLLVMType(instruction.ty);
|
|
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);
|
|
|
|
// alloca union, store tag, bitcast payload area, store payload
|
|
const tmp = c.LLVMBuildAlloca(self.builder, union_ty, "ei.tmp");
|
|
// Store tag at field 0
|
|
const tag_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 0, "ei.tagp");
|
|
_ = c.LLVMBuildStore(self.builder, tag_val, tag_ptr);
|
|
// Store payload at field 1 (bitcast the byte array to payload type)
|
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 1, "ei.pp");
|
|
const payload_typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ei.pcast");
|
|
_ = c.LLVMBuildStore(self.builder, payload_val, payload_typed_ptr);
|
|
// Load the whole union value
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, union_ty, tmp, "ei.val"));
|
|
}
|
|
},
|
|
.enum_tag => |un| {
|
|
const val = self.resolveRef(un.operand);
|
|
// Check if this is a plain enum (integer) or tagged union (struct with tag at 0)
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(val_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
// Tagged union — extract field 0 (tag)
|
|
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 {
|
|
// Plain enum — the value IS the tag
|
|
self.mapRef(val);
|
|
}
|
|
},
|
|
.enum_payload => |fa| {
|
|
const base = self.resolveRef(fa.base);
|
|
const result_ty = self.toLLVMType(instruction.ty);
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const base_kind = c.LLVMGetTypeKind(base_ty);
|
|
if (base_kind == c.LLVMStructTypeKind) {
|
|
// Tagged union: alloca, store, GEP field 1 (payload area), bitcast, load
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ep.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ep.pp");
|
|
const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ep.cast");
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, typed_ptr, "ep.val"));
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(result_ty));
|
|
}
|
|
},
|
|
|
|
// ── Union ops ────────────────────────────────────────────
|
|
.union_get => |fa| {
|
|
const base = self.resolveRef(fa.base);
|
|
const result_ty = self.toLLVMType(instruction.ty);
|
|
// Union field access: reinterpret the union's data area as the target type
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const kind = c.LLVMGetTypeKind(base_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
// Tagged union { header, payload_bytes } — access payload at field 1
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp");
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, payload_ptr, "ug.val"));
|
|
} else {
|
|
// Untagged union [N x i8] — alloca, store, reinterpret-load
|
|
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| {
|
|
const base_ptr = self.resolveRef(fa.base);
|
|
const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr));
|
|
if (base_ty_kind == c.LLVMPointerTypeKind) {
|
|
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);
|
|
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");
|
|
self.mapRef(payload_ptr);
|
|
} else {
|
|
// Untagged union — data starts at offset 0
|
|
self.mapRef(base_ptr);
|
|
}
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
},
|
|
|
|
// ── Array/Slice ops ───────────────────────────────────────
|
|
.index_get => |bin| {
|
|
const base = self.resolveRef(bin.lhs);
|
|
const idx = self.resolveRef(bin.rhs);
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const kind = c.LLVMGetTypeKind(base_ty);
|
|
if (kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) {
|
|
// Vector — use extractelement
|
|
// Coerce index to i32 if needed
|
|
const idx32 = self.coerceArg(idx, self.cached_i32);
|
|
self.mapRef(c.LLVMBuildExtractElement(self.builder, base, idx32, "ve"));
|
|
} else if (kind == c.LLVMArrayTypeKind) {
|
|
// Fixed-size array value — alloca, store, GEP, load
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ig.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
|
const elem_ty = self.toLLVMType(instruction.ty);
|
|
var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx };
|
|
const ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ig.ptr");
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val"));
|
|
} else if (kind == c.LLVMPointerTypeKind) {
|
|
// Pointer (many-pointer or raw ptr) — GEP + load
|
|
const elem_ty = self.toLLVMType(instruction.ty);
|
|
var indices = [_]c.LLVMValueRef{idx};
|
|
const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, base, &indices, 1, "ig.ptr");
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val"));
|
|
} else if (kind == c.LLVMStructTypeKind) {
|
|
// Slice/string {ptr, len} — extract ptr, GEP, load
|
|
const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ig.data");
|
|
const elem_ty = self.toLLVMType(instruction.ty);
|
|
var indices = [_]c.LLVMValueRef{idx};
|
|
const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &indices, 1, "ig.ptr");
|
|
self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val"));
|
|
} else {
|
|
// Non-aggregate base (lowering error) — emit undef
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
}
|
|
},
|
|
.index_gep => |bin| {
|
|
const base = self.resolveRef(bin.lhs);
|
|
const idx = self.resolveRef(bin.rhs);
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const kind = c.LLVMGetTypeKind(base_ty);
|
|
if (kind == c.LLVMArrayTypeKind) {
|
|
// Fixed-size array value — alloca, store, GEP
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "igp.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
|
var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx };
|
|
self.mapRef(c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "igp.ptr"));
|
|
} else if (kind == c.LLVMPointerTypeKind) {
|
|
// Pointer — GEP with proper element type
|
|
const gep_elem = blk: {
|
|
// instruction.ty is the result type (ptr to element)
|
|
// Resolve the pointee type for the GEP element size
|
|
const info = self.ir_mod.types.get(instruction.ty);
|
|
break :blk switch (info) {
|
|
.pointer => |p| self.toLLVMType(p.pointee),
|
|
.many_pointer => |p| self.toLLVMType(p.element),
|
|
else => self.cached_i8, // fallback
|
|
};
|
|
};
|
|
var indices = [_]c.LLVMValueRef{idx};
|
|
self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, base, &indices, 1, "igp.ptr"));
|
|
} else if (kind == c.LLVMStructTypeKind) {
|
|
// Slice/string {ptr, len} — extract ptr, GEP with proper element type
|
|
const data = c.LLVMBuildExtractValue(self.builder, base, 0, "igp.data");
|
|
const gep_elem = blk: {
|
|
const info = self.ir_mod.types.get(instruction.ty);
|
|
break :blk switch (info) {
|
|
.pointer => |p| self.toLLVMType(p.pointee),
|
|
.many_pointer => |p| self.toLLVMType(p.element),
|
|
else => self.cached_i8,
|
|
};
|
|
};
|
|
var indices = [_]c.LLVMValueRef{idx};
|
|
self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, data, &indices, 1, "igp.ptr"));
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
},
|
|
.length => |un| {
|
|
const val = self.resolveRef(un.operand);
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(val_ty);
|
|
if (kind == c.LLVMArrayTypeKind) {
|
|
const len = c.LLVMGetArrayLength2(val_ty);
|
|
self.mapRef(c.LLVMConstInt(self.cached_i64, len, 0));
|
|
} else if (kind == c.LLVMStructTypeKind) {
|
|
// Slice/string {ptr, len} — extract field 1 (len)
|
|
self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 1, "len"));
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_i64));
|
|
}
|
|
},
|
|
.data_ptr => |un| {
|
|
const val = self.resolveRef(un.operand);
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(val_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "dptr"));
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.cached_ptr));
|
|
}
|
|
},
|
|
.subslice => |ss| {
|
|
const base = self.resolveRef(ss.base);
|
|
var lo = self.resolveRef(ss.lo);
|
|
var hi = self.resolveRef(ss.hi);
|
|
// Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned)
|
|
if (c.LLVMTypeOf(lo) != self.cached_i64) {
|
|
lo = c.LLVMBuildZExt(self.builder, lo, self.cached_i64, "ss.lo64");
|
|
}
|
|
if (c.LLVMTypeOf(hi) != self.cached_i64) {
|
|
hi = c.LLVMBuildZExt(self.builder, hi, self.cached_i64, "ss.hi64");
|
|
}
|
|
const base_ty = c.LLVMTypeOf(base);
|
|
const base_kind = c.LLVMGetTypeKind(base_ty);
|
|
const slice_ty = self.toLLVMType(instruction.ty);
|
|
// Resolve element type from the result slice type for correct GEP stride
|
|
const elem_ty = blk: {
|
|
const info = self.ir_mod.types.get(instruction.ty);
|
|
break :blk switch (info) {
|
|
.slice => |s| self.toLLVMType(s.element),
|
|
else => self.cached_i8,
|
|
};
|
|
};
|
|
if (base_kind == c.LLVMStructTypeKind) {
|
|
// Slice/string: extract data ptr, GEP by lo
|
|
const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ss.data");
|
|
var lo_indices = [_]c.LLVMValueRef{lo};
|
|
const new_ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &lo_indices, 1, "ss.ptr");
|
|
var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len");
|
|
// Ensure length is i64 for slice struct {ptr, i64}
|
|
if (c.LLVMTypeOf(new_len) != self.cached_i64) {
|
|
new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext");
|
|
}
|
|
var result = c.LLVMGetUndef(slice_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen");
|
|
self.mapRef(result);
|
|
} else if (base_kind == c.LLVMArrayTypeKind) {
|
|
// Array: alloca, GEP to element at lo, compute len
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ss.arr");
|
|
_ = c.LLVMBuildStore(self.builder, base, tmp);
|
|
var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), lo };
|
|
const new_ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ss.ptr");
|
|
var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len");
|
|
// Ensure length is i64 for slice struct {ptr, i64}
|
|
if (c.LLVMTypeOf(new_len) != self.cached_i64) {
|
|
new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext");
|
|
}
|
|
var result = c.LLVMGetUndef(slice_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen");
|
|
self.mapRef(result);
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(slice_ty));
|
|
}
|
|
},
|
|
.array_to_slice => |un| {
|
|
const arr = self.resolveRef(un.operand);
|
|
const arr_ty = c.LLVMTypeOf(arr);
|
|
const arr_kind = c.LLVMGetTypeKind(arr_ty);
|
|
if (arr_kind == c.LLVMArrayTypeKind) {
|
|
const len = c.LLVMGetArrayLength2(arr_ty);
|
|
const tmp = c.LLVMBuildAlloca(self.builder, arr_ty, "a2s.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, arr, tmp);
|
|
var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), c.LLVMConstInt(self.cached_i64, 0, 0) };
|
|
const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, tmp, &indices, 2, "a2s.ptr");
|
|
const slice_ty = self.toLLVMType(instruction.ty);
|
|
var result = c.LLVMGetUndef(slice_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, elem_ptr, 0, "a2s.wptr");
|
|
const len_val = c.LLVMConstInt(self.cached_i64, len, 0);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, len_val, 1, "a2s.wlen");
|
|
self.mapRef(result);
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
}
|
|
},
|
|
|
|
// ── Call extensions ───────────────────────────────────────
|
|
.call_builtin => |bi| {
|
|
// Builtins that map to libc functions or LLVM intrinsics
|
|
switch (bi.builtin) {
|
|
.malloc => {
|
|
const size = self.coerceArg(self.resolveRef(bi.args[0]), self.sizeType());
|
|
const malloc_fn = self.getOrDeclareMalloc();
|
|
var args = [_]c.LLVMValueRef{size};
|
|
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMallocType(), malloc_fn, &args, 1, "malloc"));
|
|
},
|
|
.free => {
|
|
const ptr = self.resolveRef(bi.args[0]);
|
|
const free_fn = self.getOrDeclareFree();
|
|
var args = [_]c.LLVMValueRef{ptr};
|
|
_ = c.LLVMBuildCall2(self.builder, self.getFreeType(), free_fn, &args, 1, "");
|
|
self.advanceRefCounter();
|
|
},
|
|
.memcpy => {
|
|
const dst = self.resolveRef(bi.args[0]);
|
|
const src = self.resolveRef(bi.args[1]);
|
|
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
|
|
const memcpy_fn = self.getOrDeclareMemcpy();
|
|
var args = [_]c.LLVMValueRef{ dst, src, len };
|
|
_ = c.LLVMBuildCall2(self.builder, self.getMemcpyType(), memcpy_fn, &args, 3, "");
|
|
self.advanceRefCounter();
|
|
},
|
|
.memset => {
|
|
const dst = self.resolveRef(bi.args[0]);
|
|
var val = self.resolveRef(bi.args[1]);
|
|
const len = self.coerceArg(self.resolveRef(bi.args[2]), self.sizeType());
|
|
// memset expects i32 for byte value — coerce width
|
|
val = self.coerceArg(val, self.cached_i32);
|
|
const memset_fn = self.getOrDeclareMemset();
|
|
var args = [_]c.LLVMValueRef{ dst, val, len };
|
|
_ = c.LLVMBuildCall2(self.builder, self.getMemsetType(), memset_fn, &args, 3, "");
|
|
self.advanceRefCounter();
|
|
},
|
|
.sqrt, .sin, .cos, .floor => {
|
|
const val = self.resolveRef(bi.args[0]);
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const val_kind = c.LLVMGetTypeKind(val_ty);
|
|
if (val_kind == c.LLVMFloatTypeKind) {
|
|
const f = self.getOrDeclareMathF32(bi.builtin);
|
|
var args = [_]c.LLVMValueRef{val};
|
|
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF32Type(), f, &args, 1, @tagName(bi.builtin)));
|
|
} else {
|
|
const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val;
|
|
const f = self.getOrDeclareMathF64(bi.builtin);
|
|
var args = [_]c.LLVMValueRef{coerced};
|
|
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF64Type(), f, &args, 1, @tagName(bi.builtin)));
|
|
}
|
|
},
|
|
.out => {
|
|
// out(str): extract ptr and len from string fat pointer, call write(1, ptr, len)
|
|
const str_val = self.resolveRef(bi.args[0]);
|
|
const raw_ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str.ptr");
|
|
const str_len = c.LLVMBuildExtractValue(self.builder, str_val, 1, "str.len");
|
|
// On wasm32, count param is i32 (size_t)
|
|
const count = if (self.target_config.isWasm32())
|
|
c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr")
|
|
else
|
|
str_len;
|
|
const write_fn = self.getOrDeclareWrite();
|
|
var write_args = [_]c.LLVMValueRef{
|
|
c.LLVMConstInt(self.cached_i32, 1, 0), // fd = stdout
|
|
raw_ptr,
|
|
count,
|
|
};
|
|
_ = c.LLVMBuildCall2(self.builder, self.getWriteType(), write_fn, &write_args, 3, "");
|
|
self.advanceRefCounter();
|
|
},
|
|
else => {
|
|
// size_of, cast — handled by lowering or codegen glue
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
}
|
|
},
|
|
.compiler_call => {
|
|
// Compiler hooks are comptime-only; if one reaches emission, produce undef
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
.call_closure => |call_op| {
|
|
// Closure: { fn_ptr, env } — extract fn_ptr, prepend env as first arg
|
|
const closure = self.resolveRef(call_op.callee);
|
|
const cl_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(closure));
|
|
if (cl_kind != c.LLVMStructTypeKind) {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
return;
|
|
}
|
|
const fn_ptr = c.LLVMBuildExtractValue(self.builder, closure, 0, "cl.fn");
|
|
const env_ptr = c.LLVMBuildExtractValue(self.builder, closure, 1, "cl.env");
|
|
|
|
// Get the closure's declared parameter types from the IR type system
|
|
const callee_ir_ty = self.getRefIRType(call_op.callee);
|
|
const closure_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: {
|
|
if (!cty.isBuiltin()) {
|
|
const ci = self.ir_mod.types.get(cty);
|
|
if (ci == .closure) break :blk ci.closure.params;
|
|
}
|
|
break :blk null;
|
|
} else null;
|
|
|
|
// Build args: env_ptr + call args
|
|
const total_args = call_op.args.len + 1;
|
|
const args = self.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable;
|
|
defer self.alloc.free(args);
|
|
args[0] = env_ptr;
|
|
for (call_op.args, 0..) |arg_ref, j| {
|
|
args[j + 1] = self.resolveRef(arg_ref);
|
|
}
|
|
|
|
// Build function type using declared param types (not arg types)
|
|
const ret_ty = self.toLLVMType(instruction.ty);
|
|
const param_tys = self.alloc.alloc(c.LLVMTypeRef, total_args) catch unreachable;
|
|
defer self.alloc.free(param_tys);
|
|
param_tys[0] = self.cached_ptr; // env
|
|
if (closure_params) |cp| {
|
|
// Use declared closure param types and coerce args to match
|
|
// cp contains user-visible params only (no env)
|
|
for (0..call_op.args.len) |j| {
|
|
const param_ir_ty = if (j < cp.len) cp[j] else null;
|
|
if (param_ir_ty) |pty| {
|
|
const llvm_pty = self.toLLVMType(pty);
|
|
param_tys[j + 1] = llvm_pty;
|
|
args[j + 1] = self.coerceArg(args[j + 1], llvm_pty);
|
|
} else {
|
|
param_tys[j + 1] = c.LLVMTypeOf(args[j + 1]);
|
|
}
|
|
}
|
|
} else {
|
|
for (args[1..], 0..) |arg, j| {
|
|
param_tys[j + 1] = c.LLVMTypeOf(arg);
|
|
}
|
|
}
|
|
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, @intCast(total_args), 0);
|
|
|
|
const is_void = instruction.ty == .void;
|
|
const result = c.LLVMBuildCall2(self.builder, fn_ty, fn_ptr, args.ptr, @intCast(total_args), if (is_void) "" else "ccall");
|
|
if (!is_void) {
|
|
self.mapRef(result);
|
|
} else {
|
|
self.advanceRefCounter();
|
|
}
|
|
},
|
|
|
|
// ── Tuple ops ────────────────────────────────────────────
|
|
.tuple_init => |agg| {
|
|
const tuple_ty = self.toLLVMType(instruction.ty);
|
|
var result = c.LLVMGetUndef(tuple_ty);
|
|
for (agg.fields, 0..) |field_ref, i| {
|
|
const field_val = self.resolveRef(field_ref);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "ti");
|
|
}
|
|
self.mapRef(result);
|
|
},
|
|
.tuple_get => |fa| {
|
|
const base = self.resolveRef(fa.base);
|
|
self.mapRef(c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "tg"));
|
|
},
|
|
|
|
// ── Optional ops ─────────────────────────────────────────
|
|
.optional_wrap => |un| {
|
|
var val = self.resolveRef(un.operand);
|
|
const opt_ty = self.toLLVMType(instruction.ty);
|
|
const opt_kind = c.LLVMGetTypeKind(opt_ty);
|
|
if (opt_kind == c.LLVMPointerTypeKind) {
|
|
// ?*T — pointer is the optional itself (null = none)
|
|
self.mapRef(val);
|
|
} else if (opt_kind == c.LLVMStructTypeKind) {
|
|
// Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure)
|
|
const num_fields = c.LLVMCountStructElementTypes(opt_ty);
|
|
const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(opt_ty, num_fields - 1) else self.cached_i1;
|
|
if (last_field_ty == self.cached_i1) {
|
|
// ?T → { T, i1 } — wrap value + true flag
|
|
const inner_ty = c.LLVMStructGetTypeAtIndex(opt_ty, 0);
|
|
val = self.coerceArg(val, inner_ty);
|
|
var result = c.LLVMGetUndef(opt_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, val, 0, "ow.val");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, c.LLVMConstInt(self.cached_i1, 1, 0), 1, "ow.has");
|
|
self.mapRef(result);
|
|
} else {
|
|
// ?Closure → closure struct IS the optional, just pass through
|
|
self.mapRef(val);
|
|
}
|
|
} else {
|
|
self.mapRef(val);
|
|
}
|
|
},
|
|
.optional_unwrap => |un| {
|
|
const val = self.resolveRef(un.operand);
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(val_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
// Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure)
|
|
const num_fields = c.LLVMCountStructElementTypes(val_ty);
|
|
const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1;
|
|
if (last_field_ty == self.cached_i1) {
|
|
// { T, i1 } → extract field 0
|
|
self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "ou.val"));
|
|
} else {
|
|
// ?Closure → the struct itself is the value
|
|
self.mapRef(val);
|
|
}
|
|
} else {
|
|
// ?*T → pointer is the value itself
|
|
self.mapRef(val);
|
|
}
|
|
},
|
|
.optional_has_value => |un| {
|
|
const val = self.resolveRef(un.operand);
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(val_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
// Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure)
|
|
const num_fields = c.LLVMCountStructElementTypes(val_ty);
|
|
const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1;
|
|
if (last_field_ty == self.cached_i1) {
|
|
// { T, i1 } → extract has_value flag
|
|
self.mapRef(c.LLVMBuildExtractValue(self.builder, val, num_fields - 1, "oh.has"));
|
|
} else {
|
|
// ?Closure {fn_ptr, env} → check if fn_ptr is null
|
|
const fn_ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "oh.fn");
|
|
self.mapRef(c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oh.nn"));
|
|
}
|
|
} else {
|
|
// ?*T → compare with null
|
|
const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, val, c.LLVMConstNull(val_ty), "oh.nn");
|
|
self.mapRef(is_nonnull);
|
|
}
|
|
},
|
|
.optional_coalesce => |bin| {
|
|
// a ?? b — if a has value, use a's value; otherwise use b
|
|
const a = self.resolveRef(bin.lhs);
|
|
var b_val = self.resolveRef(bin.rhs);
|
|
const a_ty = c.LLVMTypeOf(a);
|
|
const kind = c.LLVMGetTypeKind(a_ty);
|
|
if (kind == c.LLVMStructTypeKind) {
|
|
const n_fields = c.LLVMCountStructElementTypes(a_ty);
|
|
const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null;
|
|
const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false;
|
|
if (is_ti1) {
|
|
// Standard optional {T, i1}: extract has_value and unwrap
|
|
const has = c.LLVMBuildExtractValue(self.builder, a, 1, "oc.has");
|
|
const unwrapped = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.val");
|
|
const uw_ty = c.LLVMTypeOf(unwrapped);
|
|
const b_ty = c.LLVMTypeOf(b_val);
|
|
if (uw_ty != b_ty) {
|
|
b_val = self.coerceArg(b_val, uw_ty);
|
|
}
|
|
self.mapRef(c.LLVMBuildSelect(self.builder, has, unwrapped, b_val, "oc.sel"));
|
|
} else {
|
|
// ?Closure {fn_ptr, env}: check if fn_ptr is null
|
|
const fn_ptr = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.fn");
|
|
const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn");
|
|
// Select the full closure struct, not just the fn_ptr
|
|
self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel"));
|
|
}
|
|
} else {
|
|
// ?*T — select on null
|
|
const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn");
|
|
self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel"));
|
|
}
|
|
},
|
|
|
|
// ── Box/Unbox Any ────────────────────────────────────────
|
|
.box_any => |ba| {
|
|
const val = self.resolveRef(ba.operand);
|
|
const any_ty = self.getAnyStructType();
|
|
// Any = { type_tag: i64, value: i64 }
|
|
const tag = c.LLVMConstInt(self.cached_i64, self.anyTag(ba.source_type), 0);
|
|
// Bitcast value to i64, using SExt for signed types, ZExt otherwise
|
|
const is_signed = self.isSignedTypeEx(ba.source_type);
|
|
const val_as_i64 = if (is_signed) self.coerceToI64Signed(val) else self.coerceToI64(val);
|
|
var result = c.LLVMGetUndef(any_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ba.tag");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, val_as_i64, 1, "ba.val");
|
|
self.mapRef(result);
|
|
},
|
|
.unbox_any => |un| {
|
|
const any_val = self.resolveRef(un.operand);
|
|
const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val));
|
|
if (any_kind == c.LLVMStructTypeKind) {
|
|
const raw = c.LLVMBuildExtractValue(self.builder, any_val, 1, "ua.raw");
|
|
const target_ty = self.toLLVMType(instruction.ty);
|
|
self.mapRef(self.coerceFromI64(raw, target_ty));
|
|
} else {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
}
|
|
},
|
|
|
|
// ── Reflection ops ──────────────────────────────────────
|
|
.field_name_get => |fr| {
|
|
// Build global string array for this struct's field names, then GEP at runtime index
|
|
const global = self.getOrBuildFieldNameArray(fr.struct_type);
|
|
const idx = self.resolveRef(fr.index);
|
|
const string_ty = self.getStringStructType();
|
|
// Get struct field count for array type
|
|
const field_info = self.ir_mod.types.get(fr.struct_type);
|
|
const field_count: u32 = switch (field_info) {
|
|
.@"struct" => |s| @intCast(s.fields.len),
|
|
.@"union" => |u| @intCast(u.fields.len),
|
|
.tagged_union => |u| @intCast(u.fields.len),
|
|
.@"enum" => |e| @intCast(e.variants.len),
|
|
else => 0,
|
|
};
|
|
const array_ty = c.LLVMArrayType(string_ty, field_count);
|
|
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
|
|
var indices = [2]c.LLVMValueRef{ zero, idx };
|
|
const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "fn.gep");
|
|
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "fn.load");
|
|
self.mapRef(result);
|
|
},
|
|
.field_value_get => |fr| {
|
|
// Switch on index, each case: extractvalue field k → box as Any
|
|
self.emitFieldValueGet(fr, func_idx);
|
|
},
|
|
|
|
// ── Switch branch ────────────────────────────────────────
|
|
.switch_br => |sw| {
|
|
const operand = self.resolveRef(sw.operand);
|
|
const default_bb = self.getBlock(func_idx, sw.default);
|
|
const switch_inst = c.LLVMBuildSwitch(self.builder, operand, default_bb, @intCast(sw.cases.len));
|
|
for (sw.cases) |case| {
|
|
const case_val = c.LLVMConstInt(c.LLVMTypeOf(operand), @bitCast(case.value), 0);
|
|
const case_bb = self.getBlock(func_idx, case.target);
|
|
c.LLVMAddCase(switch_inst, case_val, case_bb);
|
|
}
|
|
self.advanceRefCounter();
|
|
},
|
|
|
|
// ── Closure creation ─────────────────────────────────────
|
|
.closure_create => |cc| {
|
|
const fn_val = self.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.cached_ptr);
|
|
const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.cached_ptr) else self.resolveRef(cc.env);
|
|
const closure_ty = self.getClosureStructType();
|
|
var result = c.LLVMGetUndef(closure_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, fn_val, 0, "cc.fn");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, env_val, 1, "cc.env");
|
|
self.mapRef(result);
|
|
},
|
|
|
|
// ── Context ops ──────────────────────────────────────────
|
|
.context_load, .context_store, .context_save => {
|
|
// Context ops are handled by the existing codegen glue
|
|
// In the IR pipeline, these become load/store on a known global
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
.context_restore => {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
|
|
// ── Protocol ops ─────────────────────────────────────────
|
|
.protocol_call_dynamic => |pc| {
|
|
// Dynamic dispatch through vtable — extract method ptr from protocol value
|
|
const receiver = self.resolveRef(pc.receiver);
|
|
_ = receiver;
|
|
// Stub: full protocol dispatch needs vtable layout info
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
.protocol_erase => |pe| {
|
|
const concrete = self.resolveRef(pe.concrete);
|
|
_ = concrete;
|
|
// Stub: needs protocol struct construction
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
|
|
// ── Vector ops ───────────────────────────────────────────
|
|
.vec_splat => |un| {
|
|
const scalar = self.resolveRef(un.operand);
|
|
const vec_ty = self.toLLVMType(instruction.ty);
|
|
const vec_len = c.LLVMGetVectorSize(vec_ty);
|
|
// Build a splat: insertelement into undef for each lane
|
|
var result = c.LLVMGetUndef(vec_ty);
|
|
var i: c_uint = 0;
|
|
while (i < vec_len) : (i += 1) {
|
|
const idx_val = c.LLVMConstInt(self.cached_i32, i, 0);
|
|
result = c.LLVMBuildInsertElement(self.builder, result, scalar, idx_val, "splat");
|
|
}
|
|
self.mapRef(result);
|
|
},
|
|
.vec_extract => |bin| {
|
|
const vec = self.resolveRef(bin.lhs);
|
|
const idx = self.resolveRef(bin.rhs);
|
|
self.mapRef(c.LLVMBuildExtractElement(self.builder, vec, idx, "vext"));
|
|
},
|
|
.vec_insert => |tri| {
|
|
const vec = self.resolveRef(tri.a);
|
|
const idx = self.resolveRef(tri.b);
|
|
const val = self.resolveRef(tri.c);
|
|
self.mapRef(c.LLVMBuildInsertElement(self.builder, vec, val, idx, "vins"));
|
|
},
|
|
|
|
// ── Block params ─────────────────────────────────────────
|
|
.block_param => |bp| {
|
|
// Create a PHI node — incoming values are filled in by fixupPhiNodes
|
|
const ty = self.toLLVMType(instruction.ty);
|
|
const phi = c.LLVMBuildPhi(self.builder, ty, "bp");
|
|
self.pending_phis.append(self.alloc, .{
|
|
.phi = phi,
|
|
.block_id = bp.block,
|
|
.param_index = bp.param_index,
|
|
}) catch unreachable;
|
|
self.mapRef(phi);
|
|
},
|
|
|
|
// ── Misc ─────────────────────────────────────────────────
|
|
.placeholder => {
|
|
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
},
|
|
}
|
|
}
|
|
|
|
// ── Ref tracking ────────────────────────────────────────────────
|
|
|
|
fn mapRef(self: *LLVMEmitter, val: c.LLVMValueRef) void {
|
|
self.ref_map.put(self.ref_counter, val) catch unreachable;
|
|
self.ref_counter += 1;
|
|
}
|
|
|
|
fn advanceRefCounter(self: *LLVMEmitter) void {
|
|
self.ref_counter += 1;
|
|
}
|
|
|
|
fn resolveRef(self: *LLVMEmitter, ref: Ref) c.LLVMValueRef {
|
|
if (ref.isNone()) {
|
|
return c.LLVMGetUndef(self.cached_i64);
|
|
}
|
|
return self.ref_map.get(ref.index()) orelse c.LLVMGetUndef(self.cached_i64);
|
|
}
|
|
|
|
fn getBlock(self: *LLVMEmitter, func_idx: u32, block_id: BlockId) c.LLVMBasicBlockRef {
|
|
const key = makeBlockKey(func_idx, block_id.index());
|
|
return self.block_map.get(key) orelse {
|
|
std.debug.print("getBlock: missing block func={d} block={d}\n", .{ func_idx, block_id.index() });
|
|
unreachable;
|
|
};
|
|
}
|
|
|
|
// ── Struct/union GEP helper ────────────────────────────────────────
|
|
|
|
/// For struct_gep/union_gep: we need the LLVM type of the aggregate being pointed to.
|
|
/// The instruction's type is the *result* (pointer to field), so we need to look at
|
|
/// the IR instruction that produced the base pointer to find the aggregate type.
|
|
/// As a fallback, we scan back through the ref_map to find the alloca type.
|
|
fn getStructTypeForGep(self: *LLVMEmitter, instruction: *const Inst) c.LLVMTypeRef {
|
|
// For GEP, the base ref points to an alloca or another pointer.
|
|
// The instruction type is a pointer type (result of GEP), but we need the
|
|
// aggregate type. We get it from the base pointer's allocated type.
|
|
const fa = switch (instruction.op) {
|
|
.struct_gep => |f| f,
|
|
.union_gep => |f| f,
|
|
else => unreachable,
|
|
};
|
|
const base_val = self.resolveRef(fa.base);
|
|
// LLVMGetAllocatedType only works on alloca instructions
|
|
if (c.LLVMIsAAllocaInst(base_val) != null) {
|
|
const alloc_ty = c.LLVMGetAllocatedType(base_val);
|
|
if (alloc_ty != null) return alloc_ty;
|
|
}
|
|
// Fallback: trace LLVM value chain — if base came from a load,
|
|
// check the load's source pointer for an alloca
|
|
if (c.LLVMIsALoadInst(base_val) != null) {
|
|
const load_ptr = c.LLVMGetOperand(base_val, 0);
|
|
if (load_ptr != null and c.LLVMIsAAllocaInst(load_ptr) != null) {
|
|
const inner_alloc = c.LLVMGetAllocatedType(load_ptr);
|
|
if (inner_alloc != null) return inner_alloc;
|
|
}
|
|
}
|
|
// Fallback: look up the IR type of the base ref to find the pointee type
|
|
const base_ir_ty = self.getRefIRType(fa.base);
|
|
if (base_ir_ty) |ir_ty| {
|
|
if (!ir_ty.isBuiltin()) {
|
|
const info = self.ir_mod.types.get(ir_ty);
|
|
switch (info) {
|
|
.pointer => |p| return self.toLLVMType(p.pointee),
|
|
else => return self.toLLVMType(ir_ty),
|
|
}
|
|
}
|
|
}
|
|
return self.cached_i64;
|
|
}
|
|
|
|
/// Resolve the struct LLVM type for GEP operations.
|
|
/// Uses LLVM alloca type when available, falls back to IR type system.
|
|
fn resolveGepStructType(self: *LLVMEmitter, base_ref: Ref, instruction: *const Inst) c.LLVMTypeRef {
|
|
const base_val = self.resolveRef(base_ref);
|
|
|
|
// Strategy 1: base is an alloca — get allocated type directly
|
|
if (c.LLVMIsAAllocaInst(base_val) != null) {
|
|
const alloc_ty = c.LLVMGetAllocatedType(base_val);
|
|
if (alloc_ty != null) {
|
|
const kind = c.LLVMGetTypeKind(alloc_ty);
|
|
if (kind == c.LLVMStructTypeKind or kind == c.LLVMArrayTypeKind) return alloc_ty;
|
|
}
|
|
}
|
|
|
|
// Strategy 2: Use IR type system — most accurate for chained GEPs (e.g. union_gep + struct_gep)
|
|
const base_ir_ty = self.getRefIRType(base_ref);
|
|
if (base_ir_ty) |ir_ty| {
|
|
// Resolve through pointer types to find the pointee struct
|
|
var resolved = ir_ty;
|
|
if (!resolved.isBuiltin()) {
|
|
const info = self.ir_mod.types.get(resolved);
|
|
if (info == .pointer) {
|
|
resolved = info.pointer.pointee;
|
|
}
|
|
}
|
|
if (!resolved.isBuiltin()) {
|
|
return self.toLLVMType(resolved);
|
|
}
|
|
}
|
|
|
|
// Strategy 3: base is a GEP result — get the source element type
|
|
if (c.LLVMIsAGetElementPtrInst(base_val) != null) {
|
|
const src_ty = c.LLVMGetGEPSourceElementType(base_val);
|
|
if (src_ty != null) {
|
|
const kind = c.LLVMGetTypeKind(src_ty);
|
|
if (kind == c.LLVMStructTypeKind or kind == c.LLVMArrayTypeKind) return src_ty;
|
|
}
|
|
}
|
|
|
|
// Strategy 4: old fallback
|
|
_ = instruction;
|
|
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 ────────────────────────────────────────────
|
|
|
|
fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
// Determine if float by inspecting operand LLVM type
|
|
var lhs_ty = c.LLVMTypeOf(lhs);
|
|
var kind = c.LLVMGetTypeKind(lhs_ty);
|
|
var rhs_ty = c.LLVMTypeOf(rhs);
|
|
var rhs_kind = c.LLVMGetTypeKind(rhs_ty);
|
|
|
|
// Unwrap single-element struct (1-tuple) to scalar for comparison
|
|
if (kind == c.LLVMStructTypeKind and rhs_kind != c.LLVMStructTypeKind) {
|
|
if (c.LLVMCountStructElementTypes(lhs_ty) == 1) {
|
|
lhs = c.LLVMBuildExtractValue(self.builder, lhs, 0, "tup.unwrap");
|
|
lhs_ty = c.LLVMTypeOf(lhs);
|
|
kind = c.LLVMGetTypeKind(lhs_ty);
|
|
}
|
|
} else if (rhs_kind == c.LLVMStructTypeKind and kind != c.LLVMStructTypeKind) {
|
|
if (c.LLVMCountStructElementTypes(rhs_ty) == 1) {
|
|
rhs = c.LLVMBuildExtractValue(self.builder, rhs, 0, "tup.unwrap");
|
|
rhs_ty = c.LLVMTypeOf(rhs);
|
|
rhs_kind = c.LLVMGetTypeKind(rhs_ty);
|
|
}
|
|
}
|
|
|
|
// Struct types (strings, slices, tagged unions): compare fields individually
|
|
if (kind == c.LLVMStructTypeKind and rhs_kind == c.LLVMStructTypeKind) {
|
|
const n_fields = c.LLVMCountStructElementTypes(lhs_ty);
|
|
if (n_fields >= 2) {
|
|
const is_eq = (int_pred == c.LLVMIntEQ);
|
|
const f0_l = c.LLVMBuildExtractValue(self.builder, lhs, 0, "sc.l0");
|
|
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_r = c.LLVMBuildExtractValue(self.builder, rhs, 1, "sc.r1");
|
|
const cmp1 = c.LLVMBuildICmp(self.builder, @intCast(int_pred), f1_l, f1_r, "sc.c1");
|
|
const result = if (is_eq)
|
|
c.LLVMBuildAnd(self.builder, cmp0, cmp1, "sc.and")
|
|
else
|
|
c.LLVMBuildOr(self.builder, cmp0, cmp1, "sc.or");
|
|
self.mapRef(result);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Coerce operands to same type if needed
|
|
if (kind == c.LLVMIntegerTypeKind and rhs_kind == c.LLVMIntegerTypeKind) {
|
|
const lw = c.LLVMGetIntTypeWidth(lhs_ty);
|
|
const rw = c.LLVMGetIntTypeWidth(rhs_ty);
|
|
const is_unsigned = self.isRefUnsigned(bin.lhs) or self.isRefUnsigned(bin.rhs);
|
|
if (is_unsigned) {
|
|
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");
|
|
}
|
|
}
|
|
// Pointer vs integer: coerce int to null pointer
|
|
if (kind == c.LLVMPointerTypeKind and rhs_kind == c.LLVMIntegerTypeKind) {
|
|
rhs = c.LLVMConstNull(lhs_ty);
|
|
} else if (kind == c.LLVMIntegerTypeKind and rhs_kind == c.LLVMPointerTypeKind) {
|
|
lhs = c.LLVMConstNull(rhs_ty);
|
|
}
|
|
const result_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(lhs));
|
|
const result = if (result_kind == c.LLVMFloatTypeKind or result_kind == c.LLVMDoubleTypeKind)
|
|
c.LLVMBuildFCmp(self.builder, @intCast(float_pred), lhs, rhs, "fcmp")
|
|
else
|
|
c.LLVMBuildICmp(self.builder, @intCast(int_pred), lhs, rhs, "icmp");
|
|
self.mapRef(result);
|
|
}
|
|
|
|
fn emitCmpOrdered(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, signed_pred: c_uint, unsigned_pred: c_uint, float_pred: c_uint) void {
|
|
var lhs = self.resolveRef(bin.lhs);
|
|
var rhs = self.resolveRef(bin.rhs);
|
|
const lhs_ty = c.LLVMTypeOf(lhs);
|
|
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
|
|
if (kind == c.LLVMIntegerTypeKind) {
|
|
const rhs_ty = c.LLVMTypeOf(rhs);
|
|
const rhs_kind = c.LLVMGetTypeKind(rhs_ty);
|
|
if (rhs_kind == c.LLVMIntegerTypeKind) {
|
|
const lw = c.LLVMGetIntTypeWidth(lhs_ty);
|
|
const rw = c.LLVMGetIntTypeWidth(rhs_ty);
|
|
if (is_unsigned) {
|
|
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)
|
|
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
|
|
c.LLVMBuildICmp(self.builder, @intCast(signed_pred), lhs, rhs, "icmp");
|
|
self.mapRef(result);
|
|
}
|
|
|
|
/// String comparison via memcmp: compare length first, then content.
|
|
fn emitStrCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, is_eq: bool) void {
|
|
const lhs = self.resolveRef(bin.lhs);
|
|
const rhs = self.resolveRef(bin.rhs);
|
|
const b = self.builder;
|
|
const i32_ty = c.LLVMInt32TypeInContext(self.context);
|
|
const i1_ty = c.LLVMInt1TypeInContext(self.context);
|
|
const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0);
|
|
|
|
// Extract ptr and len from both fat pointers
|
|
const lhs_ptr = c.LLVMBuildExtractValue(b, lhs, 0, "str.lp");
|
|
const lhs_len = c.LLVMBuildExtractValue(b, lhs, 1, "str.ll");
|
|
const rhs_ptr = c.LLVMBuildExtractValue(b, rhs, 0, "str.rp");
|
|
const rhs_len = c.LLVMBuildExtractValue(b, rhs, 1, "str.rl");
|
|
|
|
// Compare lengths first
|
|
const len_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, lhs_len, rhs_len, "str.len_eq");
|
|
|
|
// Set up basic blocks
|
|
const cur_fn = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(b));
|
|
const memcmp_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "str.memcmp");
|
|
const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "str.merge");
|
|
|
|
const cur_bb = c.LLVMGetInsertBlock(b);
|
|
_ = c.LLVMBuildCondBr(b, len_eq, memcmp_bb, merge_bb);
|
|
|
|
// memcmp block
|
|
c.LLVMPositionBuilderAtEnd(b, memcmp_bb);
|
|
const size_ty = self.sizeType();
|
|
const memcmp_fn = c.LLVMGetNamedFunction(self.llvm_module, "memcmp") orelse blk: {
|
|
var params = [_]c.LLVMTypeRef{ ptr_ty, ptr_ty, size_ty };
|
|
const fn_type = c.LLVMFunctionType(i32_ty, ¶ms, 3, 0);
|
|
break :blk c.LLVMAddFunction(self.llvm_module, "memcmp", fn_type);
|
|
};
|
|
const cmp_len = self.coerceArg(lhs_len, size_ty);
|
|
var args = [_]c.LLVMValueRef{ lhs_ptr, rhs_ptr, cmp_len };
|
|
const fn_ty = c.LLVMGlobalGetValueType(memcmp_fn);
|
|
const cmp_result = c.LLVMBuildCall2(b, fn_ty, memcmp_fn, &args, 3, "memcmp");
|
|
const content_eq = c.LLVMBuildICmp(b, c.LLVMIntEQ, cmp_result, c.LLVMConstInt(i32_ty, 0, 0), "str.ceq");
|
|
_ = c.LLVMBuildBr(b, merge_bb);
|
|
|
|
// Merge block: phi(len_mismatch=false, memcmp_result)
|
|
c.LLVMPositionBuilderAtEnd(b, merge_bb);
|
|
const phi = c.LLVMBuildPhi(b, i1_ty, "str.eq");
|
|
const false_val = c.LLVMConstInt(i1_ty, 0, 0);
|
|
var phi_vals = [_]c.LLVMValueRef{ false_val, content_eq };
|
|
var phi_bbs = [_]c.LLVMBasicBlockRef{ cur_bb, memcmp_bb };
|
|
c.LLVMAddIncoming(phi, &phi_vals, &phi_bbs, 2);
|
|
|
|
const result = if (is_eq)
|
|
phi
|
|
else
|
|
c.LLVMBuildNot(b, phi, "str.ne");
|
|
self.mapRef(result);
|
|
}
|
|
|
|
// ── Conversion helpers ──────────────────────────────────────────
|
|
|
|
fn emitConversion(self: *LLVMEmitter, operand: c.LLVMValueRef, from: TypeId, to: TypeId, to_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
|
const from_float = isFloatOrVecFloat(from, &self.ir_mod.types);
|
|
const to_float = isFloatOrVecFloat(to, &self.ir_mod.types);
|
|
|
|
if (from_float and to_float) {
|
|
// float→float: FPExt or FPTrunc
|
|
const from_bits = floatBits(from);
|
|
const to_bits = floatBits(to);
|
|
return if (to_bits > from_bits)
|
|
c.LLVMBuildFPExt(self.builder, operand, to_ty, "fpext")
|
|
else
|
|
c.LLVMBuildFPTrunc(self.builder, operand, to_ty, "fptrunc");
|
|
}
|
|
|
|
if (from_float and !to_float) {
|
|
return if (isSignedType(to))
|
|
c.LLVMBuildFPToSI(self.builder, operand, to_ty, "fptosi")
|
|
else
|
|
c.LLVMBuildFPToUI(self.builder, operand, to_ty, "fptoui");
|
|
}
|
|
|
|
if (!from_float and to_float) {
|
|
return if (isSignedType(from))
|
|
c.LLVMBuildSIToFP(self.builder, operand, to_ty, "sitofp")
|
|
else
|
|
c.LLVMBuildUIToFP(self.builder, operand, to_ty, "uitofp");
|
|
}
|
|
|
|
// int→int: SExt, ZExt, or Trunc
|
|
const ptr_bits: u32 = @as(u32, self.ir_mod.types.pointer_size) * 8;
|
|
const from_bits = if (intBits(from) == 0) ptr_bits else intBits(from);
|
|
const to_bits = if (intBits(to) == 0) ptr_bits else intBits(to);
|
|
if (to_bits > from_bits) {
|
|
return if (isSignedType(from))
|
|
c.LLVMBuildSExt(self.builder, operand, to_ty, "sext")
|
|
else
|
|
c.LLVMBuildZExt(self.builder, operand, to_ty, "zext");
|
|
} else if (to_bits < from_bits) {
|
|
return c.LLVMBuildTrunc(self.builder, operand, to_ty, "trunc");
|
|
}
|
|
// Same width — no-op (bitcast or just return)
|
|
return operand;
|
|
}
|
|
|
|
// ── Malloc/Free declarations ────────────────────────────────────
|
|
|
|
fn getOrDeclareMalloc(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "malloc")) |f| return f;
|
|
const fn_ty = self.getMallocType();
|
|
return c.LLVMAddFunction(self.llvm_module, "malloc", fn_ty);
|
|
}
|
|
|
|
fn getOrDeclareFree(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "free")) |f| return f;
|
|
const fn_ty = self.getFreeType();
|
|
return c.LLVMAddFunction(self.llvm_module, "free", fn_ty);
|
|
}
|
|
|
|
/// Returns the LLVM type for C `size_t`: i32 on wasm32, i64 on 64-bit targets (including wasm64).
|
|
fn sizeType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
return if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64;
|
|
}
|
|
|
|
fn getMallocType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// malloc(size_t) → ptr
|
|
var param_types = [_]c.LLVMTypeRef{self.sizeType()};
|
|
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 1, 0);
|
|
}
|
|
|
|
fn getFreeType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// free(ptr) → void
|
|
var param_types = [_]c.LLVMTypeRef{self.cached_ptr};
|
|
return c.LLVMFunctionType(self.cached_void, ¶m_types, 1, 0);
|
|
}
|
|
|
|
fn getOrDeclareMemcpy(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "memcpy")) |f| return f;
|
|
return c.LLVMAddFunction(self.llvm_module, "memcpy", self.getMemcpyType());
|
|
}
|
|
|
|
fn getMemcpyType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// memcpy(ptr, ptr, size_t) → ptr
|
|
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.sizeType() };
|
|
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0);
|
|
}
|
|
|
|
fn getOrDeclareMemset(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "memset")) |f| return f;
|
|
return c.LLVMAddFunction(self.llvm_module, "memset", self.getMemsetType());
|
|
}
|
|
|
|
fn getMemsetType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// memset(ptr, i32, size_t) → ptr
|
|
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_i32, self.sizeType() };
|
|
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0);
|
|
}
|
|
|
|
fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
|
const name: [*:0]const u8 = switch (id) {
|
|
.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 getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
var param_types = [_]c.LLVMTypeRef{self.cached_f64};
|
|
return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0);
|
|
}
|
|
|
|
fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
|
const name: [*:0]const u8 = switch (id) {
|
|
.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 getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
var param_types = [_]c.LLVMTypeRef{self.cached_f32};
|
|
return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0);
|
|
}
|
|
|
|
fn getOrDeclareMemcmp(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "memcmp")) |f| return f;
|
|
// memcmp(ptr, ptr, size_t) → i32
|
|
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.sizeType() };
|
|
const fn_ty = c.LLVMFunctionType(self.cached_i32, ¶m_types, 3, 0);
|
|
return c.LLVMAddFunction(self.llvm_module, "memcmp", fn_ty);
|
|
}
|
|
|
|
fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "write")) |f| return f;
|
|
return c.LLVMAddFunction(self.llvm_module, "write", self.getWriteType());
|
|
}
|
|
|
|
fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// write(fd: i32, buf: ptr, count: size_t) → ssize_t
|
|
const st = self.sizeType();
|
|
var param_types = [_]c.LLVMTypeRef{ self.cached_i32, self.cached_ptr, st };
|
|
return c.LLVMFunctionType(st, ¶m_types, 3, 0);
|
|
}
|
|
|
|
fn getOrDeclareSnprintf(self: *LLVMEmitter) c.LLVMValueRef {
|
|
if (c.LLVMGetNamedFunction(self.llvm_module, "snprintf")) |f| return f;
|
|
return c.LLVMAddFunction(self.llvm_module, "snprintf", self.getSnprintfType());
|
|
}
|
|
|
|
fn getSnprintfType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
// snprintf(buf: ptr, size: i32, fmt: ptr, ...) → i32 (variadic)
|
|
var param_types = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_i32, self.cached_ptr };
|
|
return c.LLVMFunctionType(self.cached_i32, ¶m_types, 3, 1); // 1 = variadic
|
|
}
|
|
|
|
/// Check if a function name is a known libc builtin that has a dedicated
|
|
/// getOrDeclare* helper with correct C-compatible types.
|
|
fn isBuiltinLibcName(name: []const u8) bool {
|
|
const builtins = [_][]const u8{ "malloc", "free", "memcpy", "memset", "memcmp", "write", "snprintf" };
|
|
for (builtins) |b| {
|
|
if (std.mem.eql(u8, name, b)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Get or declare a builtin libc function by name, using the correct C-compatible type.
|
|
fn getOrDeclareBuiltinByName(self: *LLVMEmitter, name: []const u8) ?c.LLVMValueRef {
|
|
if (std.mem.eql(u8, name, "malloc")) return self.getOrDeclareMalloc();
|
|
if (std.mem.eql(u8, name, "free")) return self.getOrDeclareFree();
|
|
if (std.mem.eql(u8, name, "memcpy")) return self.getOrDeclareMemcpy();
|
|
if (std.mem.eql(u8, name, "memset")) return self.getOrDeclareMemset();
|
|
if (std.mem.eql(u8, name, "memcmp")) return self.getOrDeclareMemcmp();
|
|
if (std.mem.eql(u8, name, "write")) return self.getOrDeclareWrite();
|
|
if (std.mem.eql(u8, name, "snprintf")) return self.getOrDeclareSnprintf();
|
|
return null;
|
|
}
|
|
|
|
/// Build a string fat pointer {ptr, len} from raw pointer and length.
|
|
fn buildStringValue(self: *LLVMEmitter, ptr: c.LLVMValueRef, len: c.LLVMValueRef) c.LLVMValueRef {
|
|
const str_ty = self.getStringStructType();
|
|
const undef = c.LLVMGetUndef(str_ty);
|
|
const with_ptr = c.LLVMBuildInsertValue(self.builder, undef, ptr, 0, "s.ptr");
|
|
return c.LLVMBuildInsertValue(self.builder, with_ptr, len, 1, "s.len");
|
|
}
|
|
|
|
// ── Value coercion helpers ──────────────────────────────────────
|
|
|
|
/// Coerce any scalar value to i64 for boxing into Any.
|
|
fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
|
|
const ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(ty);
|
|
if (kind == c.LLVMVoidTypeKind) {
|
|
return c.LLVMConstInt(self.cached_i64, 0, 0);
|
|
}
|
|
if (kind == c.LLVMPointerTypeKind) {
|
|
return c.LLVMBuildPtrToInt(self.builder, val, self.cached_i64, "p2i");
|
|
}
|
|
if (kind == c.LLVMFloatTypeKind) {
|
|
// f32 → bitcast to i32 → zext to i64
|
|
const as_i32 = c.LLVMBuildBitCast(self.builder, val, self.cached_i32, "f2i32");
|
|
return c.LLVMBuildZExt(self.builder, as_i32, self.cached_i64, "z64");
|
|
}
|
|
if (kind == c.LLVMDoubleTypeKind) {
|
|
return c.LLVMBuildBitCast(self.builder, val, self.cached_i64, "d2i");
|
|
}
|
|
if (kind == c.LLVMIntegerTypeKind) {
|
|
const width = c.LLVMGetIntTypeWidth(ty);
|
|
if (width < 64) {
|
|
return c.LLVMBuildZExt(self.builder, val, self.cached_i64, "z64");
|
|
}
|
|
return val; // already i64
|
|
}
|
|
// Struct/Array/Vector types: store to alloca, ptrtoint for the pointer
|
|
if (kind == c.LLVMStructTypeKind or kind == c.LLVMArrayTypeKind or kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) {
|
|
const tmp = c.LLVMBuildAlloca(self.builder, ty, "ba.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
|
return c.LLVMBuildPtrToInt(self.builder, tmp, self.cached_i64, "ba.p2i");
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/// Coerce signed integer to i64 using sign-extension.
|
|
fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
|
|
const ty = c.LLVMTypeOf(val);
|
|
const kind = c.LLVMGetTypeKind(ty);
|
|
if (kind == c.LLVMIntegerTypeKind) {
|
|
const width = c.LLVMGetIntTypeWidth(ty);
|
|
if (width < 64) {
|
|
return c.LLVMBuildSExt(self.builder, val, self.cached_i64, "s64");
|
|
}
|
|
return val;
|
|
}
|
|
// Fallback for non-integer types
|
|
return self.coerceToI64(val);
|
|
}
|
|
|
|
/// Check if a TypeId represents a signed integer type (including arbitrary-width).
|
|
fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool {
|
|
if (isSignedType(ty)) return true;
|
|
if (!ty.isBuiltin()) {
|
|
const info = self.ir_mod.types.get(ty);
|
|
return info == .signed;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Map a TypeId to its Any tag value.
|
|
/// Uses TypeId.index() directly — this matches resolveTypeCategoryTags in lower.zig
|
|
/// which also uses TypeId indices for type-switch comparisons.
|
|
/// For arbitrary-width ints (user-defined signed/unsigned), map to the closest
|
|
/// builtin TypeId so the "case int:" branch matches correctly.
|
|
/// Map a TypeId to its Any tag value.
|
|
/// Uses TypeId.index() directly — this matches resolveTypeCategoryTags in lower.zig
|
|
/// which also uses TypeId indices for type-switch comparisons.
|
|
/// For arbitrary-width ints (user-defined signed/unsigned), map to the closest
|
|
/// builtin TypeId so the "case int:" branch matches correctly.
|
|
fn anyTag(self: *LLVMEmitter, ty: TypeId) u64 {
|
|
if (ty.isBuiltin()) return ty.index();
|
|
// For user-defined types, check if they're arbitrary-width ints
|
|
const info = self.ir_mod.types.get(ty);
|
|
return switch (info) {
|
|
.signed => |w| switch (w) {
|
|
8 => TypeId.s8.index(),
|
|
16 => TypeId.s16.index(),
|
|
32 => TypeId.s32.index(),
|
|
64 => TypeId.s64.index(),
|
|
else => if (w <= 32) TypeId.s32.index() else TypeId.s64.index(),
|
|
},
|
|
.unsigned => |w| switch (w) {
|
|
8 => TypeId.u8.index(),
|
|
16 => TypeId.u16.index(),
|
|
32 => TypeId.u32.index(),
|
|
64 => TypeId.u64.index(),
|
|
else => if (w <= 32) TypeId.u32.index() else TypeId.u64.index(),
|
|
},
|
|
else => ty.index(),
|
|
};
|
|
}
|
|
|
|
/// Coerce i64 back to the target type for unboxing from Any.
|
|
fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef {
|
|
const kind = c.LLVMGetTypeKind(target);
|
|
if (kind == c.LLVMPointerTypeKind) {
|
|
return c.LLVMBuildIntToPtr(self.builder, val, target, "i2p");
|
|
}
|
|
if (kind == c.LLVMFloatTypeKind) {
|
|
const as_i32 = c.LLVMBuildTrunc(self.builder, val, self.cached_i32, "tr32");
|
|
return c.LLVMBuildBitCast(self.builder, as_i32, target, "i2f");
|
|
}
|
|
if (kind == c.LLVMDoubleTypeKind) {
|
|
return c.LLVMBuildBitCast(self.builder, val, target, "i2d");
|
|
}
|
|
if (kind == c.LLVMIntegerTypeKind) {
|
|
const width = c.LLVMGetIntTypeWidth(target);
|
|
if (width < 64) {
|
|
return c.LLVMBuildTrunc(self.builder, val, target, "tr");
|
|
}
|
|
}
|
|
// Struct/Array/Vector types: interpret i64 as pointer, load the value
|
|
if (kind == c.LLVMStructTypeKind or kind == c.LLVMArrayTypeKind or kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) {
|
|
const ptr = c.LLVMBuildIntToPtr(self.builder, val, self.cached_ptr, "ua.ptr");
|
|
return c.LLVMBuildLoad2(self.builder, target, ptr, "ua.load");
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/// Coerce a call argument to match the expected parameter type.
|
|
/// Handles int width mismatches (trunc/ext), float width, and int↔float.
|
|
fn coerceArg(self: *LLVMEmitter, val: c.LLVMValueRef, param_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
|
const val_ty = c.LLVMTypeOf(val);
|
|
if (val_ty == param_ty) return val;
|
|
const val_kind = c.LLVMGetTypeKind(val_ty);
|
|
const param_kind = c.LLVMGetTypeKind(param_ty);
|
|
|
|
// Int → Int (width mismatch)
|
|
if (val_kind == c.LLVMIntegerTypeKind and param_kind == c.LLVMIntegerTypeKind) {
|
|
const val_w = c.LLVMGetIntTypeWidth(val_ty);
|
|
const param_w = c.LLVMGetIntTypeWidth(param_ty);
|
|
if (val_w > param_w) {
|
|
return c.LLVMBuildTrunc(self.builder, val, param_ty, "ca.tr");
|
|
} else {
|
|
// Use ZExt by default — preserves bit pattern for unsigned types.
|
|
// Signed widening is handled by explicit widen instructions from the IR.
|
|
return c.LLVMBuildZExt(self.builder, val, param_ty, "ca.ext");
|
|
}
|
|
}
|
|
// Float → Float (width mismatch)
|
|
if ((val_kind == c.LLVMFloatTypeKind or val_kind == c.LLVMDoubleTypeKind) and
|
|
(param_kind == c.LLVMFloatTypeKind or param_kind == c.LLVMDoubleTypeKind))
|
|
{
|
|
if (val_kind == c.LLVMFloatTypeKind and param_kind == c.LLVMDoubleTypeKind) {
|
|
return c.LLVMBuildFPExt(self.builder, val, param_ty, "ca.fpext");
|
|
} else {
|
|
return c.LLVMBuildFPTrunc(self.builder, val, param_ty, "ca.fptrunc");
|
|
}
|
|
}
|
|
// Int → Float (use SIToFP for i1/bool, UIToFP otherwise for safe default)
|
|
if (val_kind == c.LLVMIntegerTypeKind and (param_kind == c.LLVMFloatTypeKind or param_kind == c.LLVMDoubleTypeKind)) {
|
|
const val_w = c.LLVMGetIntTypeWidth(val_ty);
|
|
if (val_w == 1) {
|
|
return c.LLVMBuildUIToFP(self.builder, val, param_ty, "ca.uitofp");
|
|
}
|
|
// Default to SIToFP since most sx integers are signed (s64).
|
|
// Explicit unsigned conversions go through the IR widen/narrow path.
|
|
return c.LLVMBuildSIToFP(self.builder, val, param_ty, "ca.sitofp");
|
|
}
|
|
// Float → Int
|
|
if ((val_kind == c.LLVMFloatTypeKind or val_kind == c.LLVMDoubleTypeKind) and param_kind == c.LLVMIntegerTypeKind) {
|
|
return c.LLVMBuildFPToSI(self.builder, val, param_ty, "ca.fptosi");
|
|
}
|
|
// Ptr → Struct (closure auto-promotion: fn_ptr → {fn_ptr, null_env})
|
|
if (val_kind == c.LLVMPointerTypeKind and param_kind == c.LLVMStructTypeKind) {
|
|
const num_fields = c.LLVMCountStructElementTypes(param_ty);
|
|
if (num_fields == 2) {
|
|
const f0 = c.LLVMStructGetTypeAtIndex(param_ty, 0);
|
|
const f1 = c.LLVMStructGetTypeAtIndex(param_ty, 1);
|
|
if (c.LLVMGetTypeKind(f0) == c.LLVMPointerTypeKind and c.LLVMGetTypeKind(f1) == c.LLVMPointerTypeKind) {
|
|
var result = c.LLVMGetUndef(param_ty);
|
|
result = c.LLVMBuildInsertValue(self.builder, result, val, 0, "ca.cls.fn");
|
|
result = c.LLVMBuildInsertValue(self.builder, result, c.LLVMConstNull(f1), 1, "ca.cls.env");
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
// Scalar → Vector (splat: broadcast scalar to all lanes)
|
|
if (param_kind == c.LLVMVectorTypeKind or param_kind == c.LLVMScalableVectorTypeKind) {
|
|
const vec_elem_ty = c.LLVMGetElementType(param_ty);
|
|
const vec_len = c.LLVMGetVectorSize(param_ty);
|
|
// First coerce scalar to the vector element type
|
|
const scalar = self.coerceArg(val, vec_elem_ty);
|
|
// Then splat into a vector
|
|
var result = c.LLVMGetUndef(param_ty);
|
|
var lane: c_uint = 0;
|
|
while (lane < vec_len) : (lane += 1) {
|
|
const idx = c.LLVMConstInt(self.cached_i32, lane, 0);
|
|
result = c.LLVMBuildInsertElement(self.builder, result, scalar, idx, "splat");
|
|
}
|
|
return result;
|
|
}
|
|
// Struct → Ptr (string/slice decay: extract field 0 = raw pointer)
|
|
// Only for 2-field structs {ptr, i64} (fat pointers) — avoids breaking other struct→ptr cases
|
|
if (val_kind == c.LLVMStructTypeKind and param_kind == c.LLVMPointerTypeKind) {
|
|
const num_fields = c.LLVMCountStructElementTypes(val_ty);
|
|
if (num_fields == 2) {
|
|
const field0_ty = c.LLVMStructGetTypeAtIndex(val_ty, 0);
|
|
if (c.LLVMGetTypeKind(field0_ty) == c.LLVMPointerTypeKind) {
|
|
return c.LLVMBuildExtractValue(self.builder, val, 0, "ca.decay");
|
|
}
|
|
}
|
|
}
|
|
// 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");
|
|
}
|
|
// Struct → Array (C ABI coercion for 9..16-byte structs — paired with
|
|
// abiCoerceParamType's `[2 x i64]` slot for that size class). Same
|
|
// memory-bitcast pattern as the integer case; the array type carries
|
|
// 16 bytes of storage so we alloca with param_ty to guarantee size.
|
|
if (val_kind == c.LLVMStructTypeKind and param_kind == c.LLVMArrayTypeKind) {
|
|
const tmp = c.LLVMBuildAlloca(self.builder, param_ty, "abi.struct2arr");
|
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
|
return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.coerce.arr");
|
|
}
|
|
// Array → Struct (return-side counterpart for 9..16-byte structs)
|
|
if (val_kind == c.LLVMArrayTypeKind and param_kind == c.LLVMStructTypeKind) {
|
|
const tmp = c.LLVMBuildAlloca(self.builder, val_ty, "abi.arr2struct");
|
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
|
return c.LLVMBuildLoad2(self.builder, param_ty, tmp, "abi.ret.coerce.arr");
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
/// Look up the IR type of a Ref in the current function (for store coercion).
|
|
fn getRefIRType(self: *LLVMEmitter, ref: Ref) ?TypeId {
|
|
const func = &self.ir_mod.functions.items[self.current_func_idx];
|
|
const idx = ref.index();
|
|
// Check if it's a function param (refs 0..N-1)
|
|
if (idx < func.params.len) return func.params[idx].ty;
|
|
for (func.blocks.items) |blk| {
|
|
if (idx >= blk.first_ref and idx < blk.first_ref + blk.insts.items.len) {
|
|
return blk.insts.items[idx - blk.first_ref].ty;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/// Coerce both binary operands to match the instruction's result type.
|
|
/// E.g. if result is i64 but one operand is i32, sext it.
|
|
fn matchBinOpTypes(self: *LLVMEmitter, lhs: *c.LLVMValueRef, rhs: *c.LLVMValueRef, result_ty: TypeId) void {
|
|
const target = self.toLLVMType(result_ty);
|
|
lhs.* = self.coerceArg(lhs.*, target);
|
|
rhs.* = self.coerceArg(rhs.*, target);
|
|
}
|
|
|
|
// ── Type conversion ─────────────────────────────────────────────
|
|
|
|
pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef {
|
|
return switch (ty) {
|
|
.void => self.cached_void,
|
|
.bool => self.cached_i1,
|
|
.s8 => self.cached_i8,
|
|
.s16 => self.cached_i16,
|
|
.s32 => self.cached_i32,
|
|
.s64 => self.cached_i64,
|
|
.u8 => self.cached_i8,
|
|
.u16 => self.cached_i16,
|
|
.u32 => self.cached_i32,
|
|
.u64 => self.cached_i64,
|
|
.f32 => self.cached_f32,
|
|
.f64 => self.cached_f64,
|
|
.string => self.getStringStructType(),
|
|
.any => self.getAnyStructType(),
|
|
.noreturn => self.cached_void,
|
|
.isize, .usize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
|
else => self.toLLVMTypeInfo(ty),
|
|
};
|
|
}
|
|
|
|
fn toLLVMTypeInfo(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef {
|
|
const info = self.ir_mod.types.get(ty);
|
|
return switch (info) {
|
|
.signed => |w| switch (w) {
|
|
1 => self.cached_i1,
|
|
8 => self.cached_i8,
|
|
16 => self.cached_i16,
|
|
32 => self.cached_i32,
|
|
64 => self.cached_i64,
|
|
else => c.LLVMIntTypeInContext(self.context, w),
|
|
},
|
|
.unsigned => |w| switch (w) {
|
|
1 => self.cached_i1,
|
|
8 => self.cached_i8,
|
|
16 => self.cached_i16,
|
|
32 => self.cached_i32,
|
|
64 => self.cached_i64,
|
|
else => c.LLVMIntTypeInContext(self.context, w),
|
|
},
|
|
.f32 => self.cached_f32,
|
|
.f64 => self.cached_f64,
|
|
.void => self.cached_void,
|
|
.bool => self.cached_i1,
|
|
.string => self.getStringStructType(),
|
|
.pointer, .many_pointer, .function => self.cached_ptr,
|
|
.closure => self.getClosureStructType(),
|
|
.slice => self.getStringStructType(), // same {ptr, i64} layout
|
|
.optional => |opt| {
|
|
// ?*T / ?fn → bare pointer (null = none)
|
|
const child_info = self.ir_mod.types.get(opt.child);
|
|
if (child_info == .pointer or child_info == .many_pointer or child_info == .function) {
|
|
return self.cached_ptr;
|
|
}
|
|
if (child_info == .closure) {
|
|
return self.getClosureStructType();
|
|
}
|
|
// ?Protocol → protocol struct (ctx ptr = field 0 is null when none).
|
|
if (child_info == .@"struct" and child_info.@"struct".is_protocol) {
|
|
return self.toLLVMType(opt.child);
|
|
}
|
|
// ?T → { T, i1 }
|
|
var field_types: [2]c.LLVMTypeRef = .{
|
|
self.toLLVMType(opt.child),
|
|
self.cached_i1,
|
|
};
|
|
return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
|
},
|
|
.array => |arr| {
|
|
const elem = self.toLLVMType(arr.element);
|
|
return c.LLVMArrayType2(elem, arr.length);
|
|
},
|
|
.vector => |vec| {
|
|
const elem = self.toLLVMType(vec.element);
|
|
return c.LLVMVectorType(elem, vec.length);
|
|
},
|
|
.any => self.getAnyStructType(),
|
|
.noreturn => self.cached_void,
|
|
.@"struct" => |s| {
|
|
// Build LLVM struct type from fields
|
|
const n: c_uint = @intCast(s.fields.len);
|
|
const field_llvm_types = self.alloc.alloc(c.LLVMTypeRef, s.fields.len) catch unreachable;
|
|
defer self.alloc.free(field_llvm_types);
|
|
for (s.fields, 0..) |field, j| {
|
|
field_llvm_types[j] = self.toLLVMType(field.ty);
|
|
}
|
|
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
|
|
},
|
|
.@"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| {
|
|
// Untagged union — just [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;
|
|
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 = .{
|
|
header_llvm,
|
|
c.LLVMArrayType2(self.cached_i8, @intCast(max_size)),
|
|
};
|
|
return c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
|
},
|
|
.tuple => |t| {
|
|
const n: c_uint = @intCast(t.fields.len);
|
|
const field_llvm_types = self.alloc.alloc(c.LLVMTypeRef, t.fields.len) catch unreachable;
|
|
defer self.alloc.free(field_llvm_types);
|
|
for (t.fields, 0..) |f, j| {
|
|
field_llvm_types[j] = self.toLLVMType(f);
|
|
}
|
|
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
|
|
},
|
|
.protocol => {
|
|
// Protocol values: { ctx: *void, vtable_or_fn_ptrs... }
|
|
// For now, use opaque ptr
|
|
return self.cached_ptr;
|
|
},
|
|
.usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
|
};
|
|
}
|
|
|
|
// ── 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 (universal across all targets for foreign calls)
|
|
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;
|
|
}
|
|
|
|
// WASM32: usize/isize are pointer-sized (i32 on wasm32).
|
|
// Other integer types (s64, u64) keep their declared size — they represent
|
|
// genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.).
|
|
if (self.target_config.isWasm32()) {
|
|
if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
|
|
return llvm_ty;
|
|
}
|
|
|
|
// 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 composite (> 16 bytes) → pass by reference: ptr + byval(<T>) at
|
|
// the call/sig sites. LLVM's AArch64/x86_64 backend lowers byval to
|
|
// the right ABI sequence (caller copy + indirect arg).
|
|
return self.cached_ptr;
|
|
}
|
|
|
|
fn needsByval(self: *LLVMEmitter, ir_ty: TypeId, raw_llvm_ty: c.LLVMTypeRef) bool {
|
|
if (self.target_config.isWasm32()) return false;
|
|
if (ir_ty == .string) return false;
|
|
if (!ir_ty.isBuiltin()) {
|
|
const info = self.ir_mod.types.get(ir_ty);
|
|
if (info == .slice) return false;
|
|
}
|
|
if (c.LLVMGetTypeKind(raw_llvm_ty) != c.LLVMStructTypeKind) return false;
|
|
const n = c.LLVMCountStructElementTypes(raw_llvm_ty);
|
|
if (n >= 1 and n <= 4) {
|
|
var all_f = true;
|
|
var all_d = true;
|
|
var i: c_uint = 0;
|
|
while (i < n) : (i += 1) {
|
|
const ft = c.LLVMStructGetTypeAtIndex(raw_llvm_ty, i);
|
|
const fk = c.LLVMGetTypeKind(ft);
|
|
if (fk != c.LLVMFloatTypeKind) all_f = false;
|
|
if (fk != c.LLVMDoubleTypeKind) all_d = false;
|
|
}
|
|
if (all_f or all_d) return false;
|
|
}
|
|
const size = c.LLVMABISizeOfType(c.LLVMGetModuleDataLayout(self.llvm_module), raw_llvm_ty);
|
|
return size > 16;
|
|
}
|
|
|
|
fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
|
const tmp = c.LLVMBuildAlloca(self.builder, struct_ty, "byval.tmp");
|
|
_ = c.LLVMBuildStore(self.builder, val, tmp);
|
|
return tmp;
|
|
}
|
|
|
|
// ── Cached composite types ──────────────────────────────────────
|
|
|
|
fn getStringStructType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
if (self.string_struct_type) |t| return t;
|
|
var field_types = [_]c.LLVMTypeRef{
|
|
self.cached_ptr, // ptr
|
|
self.cached_i64, // len
|
|
};
|
|
self.string_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
|
return self.string_struct_type.?;
|
|
}
|
|
|
|
fn getAnyStructType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
if (self.any_struct_type) |t| return t;
|
|
var field_types = [_]c.LLVMTypeRef{
|
|
self.cached_i64, // type tag
|
|
self.cached_i64, // value
|
|
};
|
|
self.any_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
|
return self.any_struct_type.?;
|
|
}
|
|
|
|
fn getClosureStructType(self: *LLVMEmitter) c.LLVMTypeRef {
|
|
if (self.closure_struct_type) |t| return t;
|
|
var field_types = [_]c.LLVMTypeRef{
|
|
self.cached_ptr, // fn_ptr
|
|
self.cached_ptr, // env
|
|
};
|
|
self.closure_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0);
|
|
return self.closure_struct_type.?;
|
|
}
|
|
|
|
// ── 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 emitConstAggregate(self: *LLVMEmitter, agg: []const ir_inst.ConstantValue, llvm_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
|
const kind = c.LLVMGetTypeKind(llvm_ty);
|
|
const is_struct = kind == c.LLVMStructTypeKind;
|
|
const n: c_uint = @intCast(agg.len);
|
|
const vals = self.alloc.alloc(c.LLVMValueRef, agg.len) catch return c.LLVMConstNull(llvm_ty);
|
|
defer self.alloc.free(vals);
|
|
for (agg, 0..) |cv, i| {
|
|
const elem_ty = if (is_struct)
|
|
c.LLVMStructGetTypeAtIndex(llvm_ty, @intCast(i))
|
|
else
|
|
c.LLVMGetElementType(llvm_ty);
|
|
vals[i] = switch (cv) {
|
|
.int => |v| c.LLVMConstInt(elem_ty, @bitCast(v), 1),
|
|
.float => |v| c.LLVMConstReal(elem_ty, v),
|
|
.boolean => |v| c.LLVMConstInt(elem_ty, @intFromBool(v), 0),
|
|
.string => |sid| self.emitConstStringGlobal(self.ir_mod.types.getString(sid)),
|
|
.aggregate => |inner| self.emitConstAggregate(inner, elem_ty),
|
|
else => c.LLVMConstNull(elem_ty),
|
|
};
|
|
}
|
|
if (is_struct) {
|
|
return c.LLVMConstNamedStruct(llvm_ty, vals.ptr, n);
|
|
}
|
|
const elem_ty = c.LLVMGetElementType(llvm_ty);
|
|
return c.LLVMConstArray(elem_ty, vals.ptr, n);
|
|
}
|
|
|
|
fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef {
|
|
// LLVMBuildGlobalStringPtr needs a null-terminated C string
|
|
const str_z = self.alloc.dupeZ(u8, str) catch unreachable;
|
|
defer self.alloc.free(str_z);
|
|
|
|
// Create a global constant string and return a fat pointer { ptr, len }
|
|
const str_global = c.LLVMBuildGlobalStringPtr(self.builder, str_z.ptr, "str");
|
|
const len_val = c.LLVMConstInt(self.cached_i64, str.len, 0);
|
|
const str_ty = self.getStringStructType();
|
|
const undef = c.LLVMGetUndef(str_ty);
|
|
const with_ptr = c.LLVMBuildInsertValue(self.builder, undef, str_global, 0, "str.ptr");
|
|
return c.LLVMBuildInsertValue(self.builder, with_ptr, len_val, 1, "str.len");
|
|
}
|
|
|
|
// ── Reflection emission helpers ────────────────────────────────
|
|
|
|
/// Build (or return cached) a global constant array of {ptr, i64} string values
|
|
/// for the field names of a struct type.
|
|
fn getOrBuildFieldNameArray(self: *LLVMEmitter, struct_type: TypeId) c.LLVMValueRef {
|
|
if (self.field_name_arrays.get(struct_type.index())) |g| return g;
|
|
|
|
const info = self.ir_mod.types.get(struct_type);
|
|
|
|
// Collect name StringIds from struct fields, union fields, or enum variants
|
|
var name_ids = std.ArrayList(StringId).empty;
|
|
defer name_ids.deinit(self.alloc);
|
|
switch (info) {
|
|
.@"struct" => |s| {
|
|
for (s.fields) |f| name_ids.append(self.alloc, f.name) catch unreachable;
|
|
},
|
|
.@"union" => |u| {
|
|
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| {
|
|
for (e.variants) |v| name_ids.append(self.alloc, v) catch unreachable;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
const string_ty = self.getStringStructType();
|
|
const n: u32 = @intCast(name_ids.items.len);
|
|
|
|
// Build constant initializer: [N x {ptr, i64}]
|
|
var field_vals = std.ArrayList(c.LLVMValueRef).empty;
|
|
defer field_vals.deinit(self.alloc);
|
|
for (name_ids.items) |name_id| {
|
|
const name_str = self.ir_mod.types.getString(name_id);
|
|
const str_z = self.alloc.dupeZ(u8, name_str) catch unreachable;
|
|
defer self.alloc.free(str_z);
|
|
const global_str = c.LLVMAddGlobal(self.llvm_module, c.LLVMArrayType(self.cached_i8, @intCast(name_str.len + 1)), "fld.str");
|
|
c.LLVMSetInitializer(global_str, c.LLVMConstStringInContext(self.context, str_z.ptr, @intCast(name_str.len + 1), 1));
|
|
c.LLVMSetGlobalConstant(global_str, 1);
|
|
c.LLVMSetLinkage(global_str, c.LLVMPrivateLinkage);
|
|
// Build fat pointer {ptr, len} as constant struct
|
|
const len_val = c.LLVMConstInt(self.cached_i64, name_str.len, 0);
|
|
var struct_fields = [2]c.LLVMValueRef{ global_str, len_val };
|
|
const const_struct = c.LLVMConstStructInContext(self.context, &struct_fields, 2, 0);
|
|
field_vals.append(self.alloc, const_struct) catch unreachable;
|
|
}
|
|
|
|
// Create global array [N x {ptr, i64}]
|
|
const array_ty = c.LLVMArrayType(string_ty, n);
|
|
const array_init = c.LLVMConstArray(string_ty, field_vals.items.ptr, n);
|
|
const global = c.LLVMAddGlobal(self.llvm_module, array_ty, "field_names");
|
|
c.LLVMSetInitializer(global, array_init);
|
|
c.LLVMSetGlobalConstant(global, 1);
|
|
c.LLVMSetLinkage(global, c.LLVMPrivateLinkage);
|
|
|
|
self.field_name_arrays.put(struct_type.index(), global) catch unreachable;
|
|
return global;
|
|
}
|
|
|
|
/// Emit field_value_get: switch on runtime index, each case extracts a field and boxes it as Any.
|
|
fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void {
|
|
const base_val = self.resolveRef(fr.base);
|
|
const idx_val = self.resolveRef(fr.index);
|
|
|
|
const info = self.ir_mod.types.get(fr.struct_type);
|
|
const fields = switch (info) {
|
|
.@"struct" => |s| s.fields,
|
|
.@"union" => |u| u.fields,
|
|
.tagged_union => |u| u.fields,
|
|
else => &[_]TypeInfo.StructInfo.Field{},
|
|
};
|
|
|
|
if (fields.len == 0) {
|
|
// No fields (e.g., plain enum) — return void-tagged Any so payload is empty
|
|
const any_ty = self.getAnyStructType();
|
|
const void_tag = c.LLVMConstInt(self.cached_i64, TypeId.void.index(), 0);
|
|
var void_any = c.LLVMGetUndef(any_ty);
|
|
void_any = c.LLVMBuildInsertValue(self.builder, void_any, void_tag, 0, "fv.vtag");
|
|
void_any = c.LLVMBuildInsertValue(self.builder, void_any, c.LLVMConstInt(self.cached_i64, 0, 0), 1, "fv.vval");
|
|
self.mapRef(void_any);
|
|
return;
|
|
}
|
|
|
|
const any_ty = self.getAnyStructType();
|
|
const current_func = self.func_map.get(func_idx) orelse {
|
|
self.mapRef(c.LLVMGetUndef(any_ty));
|
|
return;
|
|
};
|
|
|
|
// Create merge block
|
|
const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, current_func, "fv.merge");
|
|
// Create default block (returns undef)
|
|
const default_bb = c.LLVMAppendBasicBlockInContext(self.context, current_func, "fv.default");
|
|
|
|
// Build switch
|
|
const switch_inst = c.LLVMBuildSwitch(self.builder, idx_val, default_bb, @intCast(fields.len));
|
|
|
|
// Emit case blocks
|
|
var case_blocks = std.ArrayList(c.LLVMBasicBlockRef).empty;
|
|
defer case_blocks.deinit(self.alloc);
|
|
var case_values = std.ArrayList(c.LLVMValueRef).empty;
|
|
defer case_values.deinit(self.alloc);
|
|
|
|
const is_union = info == .@"union" or info == .tagged_union;
|
|
for (fields, 0..) |field, i| {
|
|
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.LLVMPositionBuilderAtEnd(self.builder, case_bb);
|
|
var field_val: c.LLVMValueRef = undefined;
|
|
if (is_union) {
|
|
// Union: extract payload via alloca + GEP to payload area + bitcast + load
|
|
if (field.ty == .void) {
|
|
// Void variant has no payload — use zero
|
|
field_val = c.LLVMConstInt(self.cached_i64, 0, 0);
|
|
} else {
|
|
const base_ty = c.LLVMTypeOf(base_val);
|
|
const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "fv.utmp");
|
|
_ = c.LLVMBuildStore(self.builder, base_val, tmp);
|
|
const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "fv.pp");
|
|
const field_llvm_ty = self.toLLVMType(field.ty);
|
|
const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "fv.cast");
|
|
field_val = c.LLVMBuildLoad2(self.builder, field_llvm_ty, typed_ptr, "fv.field");
|
|
}
|
|
} else {
|
|
// Struct: direct extractvalue by field index
|
|
field_val = c.LLVMBuildExtractValue(self.builder, base_val, @intCast(i), "fv.field");
|
|
}
|
|
// Box as Any: {type_tag, value_as_i64}
|
|
const tag = c.LLVMConstInt(self.cached_i64, self.anyTag(field.ty), 0);
|
|
const is_field_signed = self.isSignedTypeEx(field.ty);
|
|
const val_i64 = if (is_field_signed) self.coerceToI64Signed(field_val) else self.coerceToI64(field_val);
|
|
var any_val = c.LLVMGetUndef(any_ty);
|
|
any_val = c.LLVMBuildInsertValue(self.builder, any_val, tag, 0, "fv.tag");
|
|
any_val = c.LLVMBuildInsertValue(self.builder, any_val, val_i64, 1, "fv.val");
|
|
_ = c.LLVMBuildBr(self.builder, merge_bb);
|
|
|
|
case_blocks.append(self.alloc, case_bb) catch unreachable;
|
|
case_values.append(self.alloc, any_val) catch unreachable;
|
|
}
|
|
|
|
// Default block: return undef Any
|
|
c.LLVMPositionBuilderAtEnd(self.builder, default_bb);
|
|
_ = c.LLVMBuildBr(self.builder, merge_bb);
|
|
|
|
// Merge block: PHI
|
|
c.LLVMPositionBuilderAtEnd(self.builder, merge_bb);
|
|
const phi = c.LLVMBuildPhi(self.builder, any_ty, "fv.phi");
|
|
// Add incoming from case blocks
|
|
for (case_blocks.items, case_values.items) |bb, val| {
|
|
c.LLVMAddIncoming(phi, @constCast(&val), @constCast(&bb), 1);
|
|
}
|
|
// Add incoming from default block
|
|
const undef_any = c.LLVMGetUndef(any_ty);
|
|
c.LLVMAddIncoming(phi, @constCast(&undef_any), @constCast(&default_bb), 1);
|
|
|
|
self.mapRef(phi);
|
|
}
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
|
|
fn makeBlockKey(func_idx: u32, block_idx: u32) u64 {
|
|
return (@as(u64, func_idx) << 32) | @as(u64, block_idx);
|
|
}
|
|
|
|
/// Dump the LLVM module to a string (for testing).
|
|
pub fn dumpToString(self: *LLVMEmitter) []const u8 {
|
|
const raw = c.LLVMPrintModuleToString(self.llvm_module);
|
|
return std.mem.span(raw);
|
|
}
|
|
|
|
/// Verify the LLVM module. Returns true if valid.
|
|
pub fn verify(self: *LLVMEmitter) bool {
|
|
return c.LLVMVerifyModule(self.llvm_module, c.LLVMReturnStatusAction, null) == 0;
|
|
}
|
|
|
|
/// Verify the LLVM module, returning an error message on failure.
|
|
pub fn verifyWithMessage(self: *LLVMEmitter) !void {
|
|
var err_msg: [*c]u8 = null;
|
|
if (c.LLVMVerifyModule(self.llvm_module, c.LLVMReturnStatusAction, &err_msg) != 0) {
|
|
if (err_msg != null) {
|
|
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});
|
|
c.LLVMDisposeMessage(err_msg);
|
|
}
|
|
return error.VerificationFailed;
|
|
}
|
|
}
|
|
|
|
/// Print the LLVM IR to stderr.
|
|
pub fn printIR(self: *LLVMEmitter) void {
|
|
const ir_str = c.LLVMPrintModuleToString(self.llvm_module);
|
|
defer c.LLVMDisposeMessage(ir_str);
|
|
const len = std.mem.len(ir_str);
|
|
std.debug.print("{s}\n", .{ir_str[0..len]});
|
|
}
|
|
|
|
/// Emit the module as an object file to disk.
|
|
pub fn emitObject(self: *LLVMEmitter, output_path: [*:0]const u8) !void {
|
|
return self.emitToFile(output_path, c.LLVMObjectFile);
|
|
}
|
|
|
|
/// Emit the module as an assembly file to disk.
|
|
pub fn emitAssembly(self: *LLVMEmitter, output_path: [*:0]const u8) !void {
|
|
return self.emitToFile(output_path, c.LLVMAssemblyFile);
|
|
}
|
|
|
|
/// Emit the module as LLVM bitcode to disk (for emcc to recompile with a newer LLVM).
|
|
pub fn emitBitcode(self: *LLVMEmitter, output_path: [*:0]const u8) !void {
|
|
if (c.LLVMWriteBitcodeToFile(self.llvm_module, output_path) != 0) {
|
|
return error.EmitFailed;
|
|
}
|
|
}
|
|
|
|
/// Dump the LLVM IR to a file for debugging.
|
|
pub fn dumpIRToFile(self: *LLVMEmitter, path: [*:0]const u8) void {
|
|
_ = c.LLVMPrintModuleToFile(self.llvm_module, path, null);
|
|
}
|
|
|
|
/// Emit the module as an object file to a memory buffer (for JIT).
|
|
pub fn emitObjectToMemory(self: *LLVMEmitter) !c.LLVMMemoryBufferRef {
|
|
const tm = self.target_machine orelse return error.NoTargetMachine;
|
|
var err_msg: [*c]u8 = null;
|
|
var buf: c.LLVMMemoryBufferRef = null;
|
|
if (c.LLVMTargetMachineEmitToMemoryBuffer(tm, self.llvm_module, c.LLVMObjectFile, &err_msg, &buf) != 0) {
|
|
if (err_msg != null) {
|
|
std.debug.print("failed to emit object to memory: {s}\n", .{std.mem.span(err_msg)});
|
|
c.LLVMDisposeMessage(err_msg);
|
|
}
|
|
return error.EmitFailed;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
fn emitToFile(self: *LLVMEmitter, output_path: [*:0]const u8, file_type: c.LLVMCodeGenFileType) !void {
|
|
const tm = self.target_machine orelse return error.NoTargetMachine;
|
|
var err_msg: [*c]u8 = null;
|
|
if (c.LLVMTargetMachineEmitToFile(tm, self.llvm_module, output_path, file_type, &err_msg) != 0) {
|
|
if (err_msg != null) {
|
|
std.debug.print("failed to emit file: {s}\n", .{std.mem.span(err_msg)});
|
|
c.LLVMDisposeMessage(err_msg);
|
|
}
|
|
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();
|
|
// Check function parameters first (refs 0..N-1)
|
|
if (ref_idx < func.params.len) {
|
|
const ty = func.params[ref_idx].ty;
|
|
return ty == .u8 or ty == .u16 or ty == .u32 or ty == .u64;
|
|
}
|
|
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 ─────────────────────────────────────
|
|
|
|
fn isFloatType(ty: TypeId) bool {
|
|
return ty == .f32 or ty == .f64;
|
|
}
|
|
|
|
/// Check if a TypeId is a float type, including float vectors.
|
|
fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool {
|
|
if (ty == .f32 or ty == .f64) return true;
|
|
if (!ty.isBuiltin()) {
|
|
const info = types.get(ty);
|
|
if (info == .vector) return info.vector.element == .f32 or info.vector.element == .f64;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn isSignedType(ty: TypeId) bool {
|
|
return switch (ty) {
|
|
.s8, .s16, .s32, .s64, .isize => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
fn floatBits(ty: TypeId) u32 {
|
|
return switch (ty) {
|
|
.f32 => 32,
|
|
.f64 => 64,
|
|
else => 0,
|
|
};
|
|
}
|
|
|
|
fn intBits(ty: TypeId) u32 {
|
|
return switch (ty) {
|
|
.s8, .u8 => 8,
|
|
.s16, .u16 => 16,
|
|
.s32, .u32 => 32,
|
|
.s64, .u64 => 64,
|
|
.bool => 1,
|
|
.usize, .isize => 0, // target-dependent — caller must query pointer_size
|
|
else => 64,
|
|
};
|
|
}
|