From 0e7bae563a449545690ad0457b2ff6dfa4058862 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:41:39 +0300 Subject: [PATCH] refactor(backend): drain remaining emitInst handlers into ops.zig (A7.4 slice e) Move the final inline emitInst handler groups (terminators, box/unbox-Any, reflection, switch-branch, closure-creation, vector, block-param, misc) into the Ops facade in src/backend/llvm/ops.zig. emitInst is now pure dispatch: every arm delegates to self.ops().*, leaving only setInstDebugLocation plus one-line delegations. Widen the shared infra the moved bodies reach (emitFailableMainRet, getBlock, anyTag, isSignedTypeEx, coerceToI64/coerceToI64Signed/coerceFromI64, emitFieldValueGet) to pub on LLVMEmitter; helper and ref-tracking sections stay put. Pure relocation: emitted LLVM IR byte-identical, zero snapshot churn. --- src/backend/llvm/ops.zig | 273 +++++++++++++++++++++++++++++++++++++-- src/ir/emit_llvm.zig | 250 ++++------------------------------- 2 files changed, 288 insertions(+), 235 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index ee5e639..e34bbb1 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -25,21 +25,32 @@ const CallIndirect = ir_inst.CallIndirect; const ObjcMsgSend = ir_inst.ObjcMsgSend; const JniMsgSend = ir_inst.JniMsgSend; const BuiltinCall = ir_inst.BuiltinCall; +const TriOp = ir_inst.TriOp; +const Branch = ir_inst.Branch; +const CondBranch = ir_inst.CondBranch; +const SwitchBranch = ir_inst.SwitchBranch; +const BoxAny = ir_inst.BoxAny; +const ClosureCreate = ir_inst.ClosureCreate; +const BlockParam = ir_inst.BlockParam; +const FieldReflect = ir_inst.FieldReflect; const TypeId = ir_types.TypeId; const StringId = ir_types.StringId; -/// Instruction-emission handlers for `emitInst`: the constant, arithmetic, -/// bitwise, comparison, logical, memory, globals, conversion, pointer, and -/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler, -/// and closure calls), plus the aggregate ops (struct, enum, union, -/// array/slice, tuple, and optional). A backend `*LLVMEmitter` facade -/// (field `e`): each method emits one opcode's LLVM IR via `self.e.*`. The -/// shared infra these bodies call back into (`mapRef`/`resolveRef`/ -/// `matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/ +/// Instruction-emission handlers for `emitInst`: every opcode group — the +/// constant, arithmetic, bitwise, comparison, logical, memory, globals, +/// conversion, pointer, and call opcodes (direct/indirect/objc/jni dispatch +/// plus builtin, compiler, and closure calls), the aggregate ops (struct, +/// enum, union, array/slice, tuple, and optional), and the terminators, +/// box/unbox-Any, reflection, switch-branch, closure-creation, vector, +/// block-param, and misc ops. A backend `*LLVMEmitter` facade (field `e`): +/// each method emits one opcode's LLVM IR via `self.e.*`. The shared infra +/// these bodies call back into (`mapRef`/`resolveRef`/`advanceRefCounter`/ +/// `getBlock`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/ /// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ /// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/ -/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`. -/// `emitInst`'s arms reach these via `self.ops()`. +/// `emitFailableMainRet`/`emitFieldValueGet`/`resolveAggregate`/ +/// `resolveGepStructType`) stays on `LLVMEmitter`. `emitInst`'s arms reach +/// these via `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -1689,4 +1700,246 @@ pub const Ops = struct { self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); } } + + // ── Terminators ──────────────────────────────────────── + pub fn emitRet(self: Ops, un: UnaryOp) void { + var val = self.e.resolveRef(un.operand); + const func = &self.e.ir_mod.functions.items[self.e.current_func_idx]; + // Failable main: wrap the return in the entry-point reporter + // (ERR E4.2) — exit 0 (or the value) on success, else print the + // trace + tag to stderr and exit 1 — instead of returning the + // tag/tuple as the raw exit code. Two shapes: + // `-> !` → `val` is the bare u32 error tag. + // `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both. + if (self.e.current_func_is_main) { + const rinfo = self.e.ir_mod.types.get(func.ret); + if (rinfo == .error_set) { + self.e.emitFailableMainRet(null, val); + self.e.advanceRefCounter(); + return; + } + if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and + self.e.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set) + { + const value = c.LLVMBuildExtractValue(self.e.builder, val, 0, "main.ret.val"); + const tag = c.LLVMBuildExtractValue(self.e.builder, val, 1, "main.ret.tag"); + self.e.emitFailableMainRet(value, tag); + self.e.advanceRefCounter(); + return; + } + } + // sret-shaped function: declared return-type-in-IR is + // the struct, but the LLVM signature is void with a + // prepended ptr sret param. Store the value through + // the sret slot and emit ret void. + const needs_c_abi = func.is_extern or func.call_conv == .c; + const raw_ret = self.e.toLLVMType(func.ret); + if (needs_c_abi and self.e.needsByval(func.ret, raw_ret)) { + const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder)); + const sret_ptr = c.LLVMGetParam(llvm_func2, 0); + _ = c.LLVMBuildStore(self.e.builder, val, sret_ptr); + _ = c.LLVMBuildRetVoid(self.e.builder); + self.e.advanceRefCounter(); + return; + } + // Coerce return value to match the function's LLVM return type + const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder)); + const fn_ty = c.LLVMGlobalGetValueType(llvm_func); + const expected_ret = c.LLVMGetReturnType(fn_ty); + val = self.e.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.e.builder, val); + self.e.advanceRefCounter(); + } + + pub fn emitRetVoid(self: Ops) void { + if (self.e.current_func_is_main) { + // main must return i32 0 for JIT + _ = c.LLVMBuildRet(self.e.builder, c.LLVMConstInt(self.e.cached_i32, 0, 0)); + } else { + _ = c.LLVMBuildRetVoid(self.e.builder); + } + self.e.advanceRefCounter(); + } + + pub fn emitUnreachable(self: Ops) void { + _ = c.LLVMBuildUnreachable(self.e.builder); + self.e.advanceRefCounter(); + } + + pub fn emitBr(self: Ops, branch: Branch, func_idx: u32) void { + const target = self.e.getBlock(func_idx, branch.target); + _ = c.LLVMBuildBr(self.e.builder, target); + self.e.advanceRefCounter(); + } + + pub fn emitCondBr(self: Ops, cbr: CondBranch, func_idx: u32) void { + var cond = self.e.resolveRef(cbr.cond); + const then_bb = self.e.getBlock(func_idx, cbr.then_target); + const else_bb = self.e.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.e.cached_i1) { + if (cond_kind == c.LLVMPointerTypeKind) { + cond = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool"); + } else if (cond_kind == c.LLVMIntegerTypeKind) { + cond = c.LLVMBuildICmp(self.e.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.e.cached_i1, 1, 0); + } else { + cond = c.LLVMConstInt(self.e.cached_i1, 1, 0); // default truthy + } + } + _ = c.LLVMBuildCondBr(self.e.builder, cond, then_bb, else_bb); + self.e.advanceRefCounter(); + } + + // ── Box/Unbox Any ────────────────────────────────────── + pub fn emitBoxAny(self: Ops, ba: BoxAny) void { + const val = self.e.resolveRef(ba.operand); + const any_ty = self.e.getAnyStructType(); + // Any = { type_tag: i64, value: i64 } + const tag = c.LLVMConstInt(self.e.cached_i64, self.e.anyTag(ba.source_type), 0); + // Bitcast value to i64, using SExt for signed types, ZExt otherwise + const is_signed = self.e.isSignedTypeEx(ba.source_type); + const val_as_i64 = if (is_signed) self.e.coerceToI64Signed(val) else self.e.coerceToI64(val); + var result = c.LLVMGetUndef(any_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ba.tag"); + result = c.LLVMBuildInsertValue(self.e.builder, result, val_as_i64, 1, "ba.val"); + self.e.mapRef(result); + } + + pub fn emitUnboxAny(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const any_val = self.e.resolveRef(un.operand); + const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val)); + if (any_kind == c.LLVMStructTypeKind) { + const raw = c.LLVMBuildExtractValue(self.e.builder, any_val, 1, "ua.raw"); + const target_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(self.e.coerceFromI64(raw, target_ty)); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + // ── Reflection ops ───────────────────────────────────── + pub fn emitFieldNameGet(self: Ops, fr: FieldReflect) void { + // Build global string array for this struct's field names, then GEP at runtime index + const global = self.e.reflection().getOrBuildFieldNameArray(fr.struct_type); + const idx = self.e.resolveRef(fr.index); + const string_ty = self.e.getStringStructType(); + // Get struct field count for array type + const field_info = self.e.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.e.cached_i64, 0, 0); + var indices = [2]c.LLVMValueRef{ zero, idx }; + const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "fn.gep"); + const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "fn.load"); + self.e.mapRef(result); + } + + pub fn emitFieldValueGet(self: Ops, fr: FieldReflect, func_idx: u32) void { + // Switch on index, each case: extractvalue field k → box as Any + self.e.emitFieldValueGet(fr, func_idx); + } + + pub fn emitErrorTagNameGet(self: Ops, u: UnaryOp) void { + // Tag id → name: GEP into the always-linked tag-name table at + // the runtime tag id (the error-set value, a u32). Out-of-range + // ids can't occur — ids come from the same registry the table + // is built from — so no bounds branch is needed. + const global = self.e.reflection().getOrBuildTagNameArray(); + const tag_raw = self.e.resolveRef(u.operand); + const idx = c.LLVMBuildZExt(self.e.builder, tag_raw, self.e.cached_i64, "etn.idx"); + const string_ty = self.e.getStringStructType(); + const n: u32 = @intCast(self.e.ir_mod.types.tags.names.items.len); + const array_ty = c.LLVMArrayType(string_ty, n); + const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0); + var indices = [2]c.LLVMValueRef{ zero, idx }; + const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "etn.gep"); + const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "etn.load"); + self.e.mapRef(result); + } + + // ── Switch branch ────────────────────────────────────── + pub fn emitSwitchBr(self: Ops, sw: SwitchBranch, func_idx: u32) void { + const operand = self.e.resolveRef(sw.operand); + const default_bb = self.e.getBlock(func_idx, sw.default); + const switch_inst = c.LLVMBuildSwitch(self.e.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.e.getBlock(func_idx, case.target); + c.LLVMAddCase(switch_inst, case_val, case_bb); + } + self.e.advanceRefCounter(); + } + + // ── Closure creation ─────────────────────────────────── + pub fn emitClosureCreate(self: Ops, cc: ClosureCreate) void { + const fn_val = self.e.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.e.cached_ptr); + const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.e.cached_ptr) else self.e.resolveRef(cc.env); + const closure_ty = self.e.getClosureStructType(); + var result = c.LLVMGetUndef(closure_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, fn_val, 0, "cc.fn"); + result = c.LLVMBuildInsertValue(self.e.builder, result, env_val, 1, "cc.env"); + self.e.mapRef(result); + } + + // ── Vector ops ───────────────────────────────────────── + pub fn emitVecSplat(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const scalar = self.e.resolveRef(un.operand); + const vec_ty = self.e.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.e.cached_i32, i, 0); + result = c.LLVMBuildInsertElement(self.e.builder, result, scalar, idx_val, "splat"); + } + self.e.mapRef(result); + } + + pub fn emitVecExtract(self: Ops, bin: BinOp) void { + const vec = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildExtractElement(self.e.builder, vec, idx, "vext")); + } + + pub fn emitVecInsert(self: Ops, tri: TriOp) void { + const vec = self.e.resolveRef(tri.a); + const idx = self.e.resolveRef(tri.b); + const val = self.e.resolveRef(tri.c); + self.e.mapRef(c.LLVMBuildInsertElement(self.e.builder, vec, val, idx, "vins")); + } + + // ── Block params ─────────────────────────────────────── + pub fn emitBlockParam(self: Ops, instruction: *const Inst, bp: BlockParam) void { + // Create a PHI node — incoming values are filled in by fixupPhiNodes + const ty = self.e.toLLVMType(instruction.ty); + const phi = c.LLVMBuildPhi(self.e.builder, ty, "bp"); + self.e.pending_phis.append(self.e.alloc, .{ + .phi = phi, + .block_id = bp.block, + .param_index = bp.param_index, + }) catch unreachable; + self.e.mapRef(phi); + } + + // ── Misc ─────────────────────────────────────────────── + pub fn emitPlaceholder(self: Ops, instruction: *const Inst) void { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 5a9af27..78853d2 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1417,99 +1417,11 @@ pub const LLVMEmitter = struct { .call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op), // ── Terminators ──────────────────────────────────────── - .ret => |un| { - var val = self.resolveRef(un.operand); - const func = &self.ir_mod.functions.items[self.current_func_idx]; - // Failable main: wrap the return in the entry-point reporter - // (ERR E4.2) — exit 0 (or the value) on success, else print the - // trace + tag to stderr and exit 1 — instead of returning the - // tag/tuple as the raw exit code. Two shapes: - // `-> !` → `val` is the bare u32 error tag. - // `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both. - if (self.current_func_is_main) { - const rinfo = self.ir_mod.types.get(func.ret); - if (rinfo == .error_set) { - self.emitFailableMainRet(null, val); - self.advanceRefCounter(); - return; - } - if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and - self.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set) - { - const value = c.LLVMBuildExtractValue(self.builder, val, 0, "main.ret.val"); - const tag = c.LLVMBuildExtractValue(self.builder, val, 1, "main.ret.tag"); - self.emitFailableMainRet(value, tag); - self.advanceRefCounter(); - return; - } - } - // sret-shaped function: declared return-type-in-IR is - // the struct, but the LLVM signature is void with a - // prepended ptr sret param. Store the value through - // the sret slot and emit ret void. - const needs_c_abi = func.is_extern or func.call_conv == .c; - const raw_ret = self.toLLVMType(func.ret); - if (needs_c_abi and self.needsByval(func.ret, raw_ret)) { - const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); - const sret_ptr = c.LLVMGetParam(llvm_func2, 0); - _ = c.LLVMBuildStore(self.builder, val, sret_ptr); - _ = c.LLVMBuildRetVoid(self.builder); - self.advanceRefCounter(); - return; - } - // 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(); - }, + .ret => |un| self.ops().emitRet(un), + .ret_void => self.ops().emitRetVoid(), + .@"unreachable" => self.ops().emitUnreachable(), + .br => |branch| self.ops().emitBr(branch, func_idx), + .cond_br => |cbr| self.ops().emitCondBr(cbr, func_idx), // ── Struct ops ──────────────────────────────────────────── .struct_init => |agg| self.ops().emitStructInit(instruction, agg), @@ -1549,142 +1461,30 @@ pub const LLVMEmitter = struct { .optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin), // ── 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))); - } - }, + .box_any => |ba| self.ops().emitBoxAny(ba), + .unbox_any => |un| self.ops().emitUnboxAny(instruction, un), // ── Reflection ops ────────────────────────────────────── - .field_name_get => |fr| { - // Build global string array for this struct's field names, then GEP at runtime index - const global = self.reflection().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); - }, - .error_tag_name_get => |u| { - // Tag id → name: GEP into the always-linked tag-name table at - // the runtime tag id (the error-set value, a u32). Out-of-range - // ids can't occur — ids come from the same registry the table - // is built from — so no bounds branch is needed. - const global = self.reflection().getOrBuildTagNameArray(); - const tag_raw = self.resolveRef(u.operand); - const idx = c.LLVMBuildZExt(self.builder, tag_raw, self.cached_i64, "etn.idx"); - const string_ty = self.getStringStructType(); - const n: u32 = @intCast(self.ir_mod.types.tags.names.items.len); - const array_ty = c.LLVMArrayType(string_ty, n); - 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, "etn.gep"); - const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "etn.load"); - self.mapRef(result); - }, + .field_name_get => |fr| self.ops().emitFieldNameGet(fr), + .field_value_get => |fr| self.ops().emitFieldValueGet(fr, func_idx), + .error_tag_name_get => |u| self.ops().emitErrorTagNameGet(u), // ── 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(); - }, + .switch_br => |sw| self.ops().emitSwitchBr(sw, func_idx), // ── 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); - }, + .closure_create => |cc| self.ops().emitClosureCreate(cc), // ── 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")); - }, + .vec_splat => |un| self.ops().emitVecSplat(instruction, un), + .vec_extract => |bin| self.ops().emitVecExtract(bin), + .vec_insert => |tri| self.ops().emitVecInsert(tri), // ── 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); - }, + .block_param => |bp| self.ops().emitBlockParam(instruction, bp), // ── Misc ───────────────────────────────────────────────── - .placeholder => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - }, + .placeholder => self.ops().emitPlaceholder(instruction), } } @@ -1706,7 +1506,7 @@ pub const LLVMEmitter = struct { 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 { + pub 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() }); @@ -2180,7 +1980,7 @@ pub const LLVMEmitter = struct { // ── Value coercion helpers ────────────────────────────────────── /// Coerce any scalar value to i64 for boxing into Any. - fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { + pub fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { const ty = c.LLVMTypeOf(val); const kind = c.LLVMGetTypeKind(ty); if (kind == c.LLVMVoidTypeKind) { @@ -2214,7 +2014,7 @@ pub const LLVMEmitter = struct { } /// Coerce signed integer to i64 using sign-extension. - fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { + pub fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef { const ty = c.LLVMTypeOf(val); const kind = c.LLVMGetTypeKind(ty); if (kind == c.LLVMIntegerTypeKind) { @@ -2229,7 +2029,7 @@ pub const LLVMEmitter = struct { } /// Check if a TypeId represents a signed integer type (including arbitrary-width). - fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool { + pub fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool { if (isSignedType(ty)) return true; if (!ty.isBuiltin()) { const info = self.ir_mod.types.get(ty); @@ -2248,7 +2048,7 @@ pub const LLVMEmitter = struct { /// 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 { + pub 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); @@ -2272,7 +2072,7 @@ pub const LLVMEmitter = struct { } /// Coerce i64 back to the target type for unboxing from Any. - fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef { + pub 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"); @@ -2679,7 +2479,7 @@ pub const LLVMEmitter = struct { /// name from the always-linked tag-name table, hand it + the tag to /// `sx_trace_report_unhandled` (prints the header + return trace to stderr), /// and `ret i32 1`. - fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void { + pub fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void { const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder)); const tag_i32 = self.coerceArg(tag_val, self.cached_i32); @@ -2719,7 +2519,7 @@ pub const LLVMEmitter = struct { } /// 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 { + pub 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);