diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig new file mode 100644 index 0000000..1f627e2 --- /dev/null +++ b/src/backend/llvm/ops.zig @@ -0,0 +1,289 @@ +const std = @import("std"); +const llvm = @import("../../llvm_api.zig"); +const c = llvm.c; +const emit = @import("../../ir/emit_llvm.zig"); +const ir_inst = @import("../../ir/inst.zig"); +const ir_types = @import("../../ir/types.zig"); + +const LLVMEmitter = emit.LLVMEmitter; +const Inst = ir_inst.Inst; +const BinOp = ir_inst.BinOp; +const UnaryOp = ir_inst.UnaryOp; +const TypeId = ir_types.TypeId; +const StringId = ir_types.StringId; + +/// Scalar instruction-emission handlers for `emitInst`: the constant, +/// arithmetic, bitwise, comparison, and logical opcodes. 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`/`emitStringConstant`/`reflection`) stays on `LLVMEmitter`. +/// `emitInst`'s scalar arms reach these via `self.ops()`. +pub const Ops = struct { + e: *LLVMEmitter, + + // ── Constants ─────────────────────────────────────────── + pub fn emitConstInt(self: Ops, instruction: *const Inst, val: i64) void { + const ty = self.e.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.e.context), 0, 0); + self.e.mapRef(llvm_val); + } + + pub fn emitConstFloat(self: Ops, instruction: *const Inst, val: f64) void { + const ty = self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMConstReal(ty, val); + self.e.mapRef(llvm_val); + } + + pub fn emitConstBool(self: Ops, val: bool) void { + const llvm_val = c.LLVMConstInt(self.e.cached_i1, @intFromBool(val), 0); + self.e.mapRef(llvm_val); + } + + pub fn emitIsComptime(self: Ops) void { + // Compiled code is never the comptime interpreter → constant + // `false`. A `if is_comptime() { … }` branch becomes dead. + self.e.mapRef(c.LLVMConstInt(self.e.cached_i1, 0, 0)); + } + + pub fn emitInterpPrintFrames(self: Ops) void { + // No interpreter stack in compiled code; this only ever sits in + // a dead `is_comptime()` branch. Emit nothing. + self.e.advanceRefCounter(); + } + + pub fn emitTraceFrame(self: Ops, instruction: *const Inst) void { + self.e.mapRef(self.e.reflection().emitTraceFrame(instruction)); + } + + pub fn emitTraceResolve(self: Ops, u: UnaryOp) void { + // The operand is a `Frame*` stamped in by `.trace_frame` (as + // i64); reinterpret and load it. + const raw = self.e.resolveRef(u.operand); + const frame_ty = self.e.getFrameStructType(); + const ptr = c.LLVMBuildIntToPtr(self.e.builder, raw, self.e.cached_ptr, "frame.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, frame_ty, ptr, "frame.val")); + } + + pub fn emitConstString(self: Ops, str_id: StringId) void { + const str = self.e.ir_mod.types.getString(str_id); + const llvm_val = self.e.emitStringConstant(str); + self.e.mapRef(llvm_val); + } + + pub fn emitConstNull(self: Ops, instruction: *const Inst) void { + const ty = if (instruction.ty == .void) self.e.cached_ptr else self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMConstNull(ty); + self.e.mapRef(llvm_val); + } + + pub fn emitConstUndef(self: Ops, instruction: *const Inst) void { + if (instruction.ty == .void) { + // void has no value — map to undef i64 as placeholder + self.e.mapRef(c.LLVMGetUndef(self.e.cached_i64)); + } else { + const ty = self.e.toLLVMType(instruction.ty); + const llvm_val = c.LLVMGetUndef(ty); + self.e.mapRef(llvm_val); + } + } + + pub fn emitConstType(self: Ops, tid: TypeId) void { + // Type values are Any-shaped pairs: + // { tag = .any.index() (the meta-marker), + // value = tid.index() } + // Lets storage in Any slots, struct fields, + // `Type`-typed vars, and slice elements all round- + // trip through the standard Any infrastructure. + // `case type:` in `any_to_string` matches on + // tag == `.any.index()`. Runtime `type_name(t)` + // extracts the value field and indexes into the + // type-name lookup table. + const any_ty = self.e.getAnyStructType(); + const tag = c.LLVMConstInt(self.e.cached_i64, TypeId.any.index(), 0); + const val = c.LLVMConstInt(self.e.cached_i64, tid.index(), 0); + var result = c.LLVMGetUndef(any_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ct.tag"); + result = c.LLVMBuildInsertValue(self.e.builder, result, val, 1, "ct.val"); + self.e.mapRef(result); + } + + // ── Arithmetic ───────────────────────────────────────── + pub fn emitAdd(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFAdd(self.e.builder, lhs, rhs, "fadd") + else + c.LLVMBuildAdd(self.e.builder, lhs, rhs, "add"); + self.e.mapRef(result); + } + + pub fn emitSub(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFSub(self.e.builder, lhs, rhs, "fsub") + else + c.LLVMBuildSub(self.e.builder, lhs, rhs, "sub"); + self.e.mapRef(result); + } + + pub fn emitMul(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFMul(self.e.builder, lhs, rhs, "fmul") + else + c.LLVMBuildMul(self.e.builder, lhs, rhs, "mul"); + self.e.mapRef(result); + } + + pub fn emitDiv(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFDiv(self.e.builder, lhs, rhs, "fdiv") + else if (emit.isSignedType(instruction.ty)) + c.LLVMBuildSDiv(self.e.builder, lhs, rhs, "sdiv") + else + c.LLVMBuildUDiv(self.e.builder, lhs, rhs, "udiv"); + self.e.mapRef(result); + } + + pub fn emitMod(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFRem(self.e.builder, lhs, rhs, "fmod") + else if (emit.isSignedType(instruction.ty)) + c.LLVMBuildSRem(self.e.builder, lhs, rhs, "srem") + else + c.LLVMBuildURem(self.e.builder, lhs, rhs, "urem"); + self.e.mapRef(result); + } + + pub fn emitNeg(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + const is_float = emit.isFloatOrVecFloat(instruction.ty, &self.e.ir_mod.types); + const result = if (is_float) + c.LLVMBuildFNeg(self.e.builder, operand, "fneg") + else + c.LLVMBuildNeg(self.e.builder, operand, "neg"); + self.e.mapRef(result); + } + + // ── Bitwise ──────────────────────────────────────────── + pub fn emitBitAnd(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildAnd(self.e.builder, lhs, rhs, "and")); + } + + pub fn emitBitOr(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildOr(self.e.builder, lhs, rhs, "or")); + } + + pub fn emitBitXor(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildXor(self.e.builder, lhs, rhs, "xor")); + } + + pub fn emitBitNot(self: Ops, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "not")); + } + + pub fn emitShl(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + self.e.mapRef(c.LLVMBuildShl(self.e.builder, lhs, rhs, "shl")); + } + + pub fn emitShr(self: Ops, instruction: *const Inst, bin: BinOp) void { + var lhs = self.e.resolveRef(bin.lhs); + var rhs = self.e.resolveRef(bin.rhs); + self.e.matchBinOpTypes(&lhs, &rhs, instruction.ty); + // Use arithmetic shift right for signed, logical for unsigned + const result = if (emit.isSignedType(instruction.ty)) + c.LLVMBuildAShr(self.e.builder, lhs, rhs, "ashr") + else + c.LLVMBuildLShr(self.e.builder, lhs, rhs, "lshr"); + self.e.mapRef(result); + } + + // ── Comparisons ─────────────────────────────────────── + pub fn emitCmpEq(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmp(bin, instruction.ty, c.LLVMIntEQ, c.LLVMRealOEQ); + } + + pub fn emitCmpNe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmp(bin, instruction.ty, c.LLVMIntNE, c.LLVMRealONE); + } + + pub fn emitCmpLt(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLT, c.LLVMIntULT, c.LLVMRealOLT); + } + + pub fn emitCmpLe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSLE, c.LLVMIntULE, c.LLVMRealOLE); + } + + pub fn emitCmpGt(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGT, c.LLVMIntUGT, c.LLVMRealOGT); + } + + pub fn emitCmpGe(self: Ops, instruction: *const Inst, bin: BinOp) void { + self.e.emitCmpOrdered(bin, instruction.ty, c.LLVMIntSGE, c.LLVMIntUGE, c.LLVMRealOGE); + } + + pub fn emitStrEq(self: Ops, bin: BinOp) void { + self.e.emitStrCmp(bin, true); + } + + pub fn emitStrNe(self: Ops, bin: BinOp) void { + self.e.emitStrCmp(bin, false); + } + + // ── Logical ─────────────────────────────────────────── + pub fn emitBoolAnd(self: Ops, bin: BinOp) void { + const lhs = self.e.resolveRef(bin.lhs); + const rhs = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildAnd(self.e.builder, lhs, rhs, "land")); + } + + pub fn emitBoolOr(self: Ops, bin: BinOp) void { + const lhs = self.e.resolveRef(bin.lhs); + const rhs = self.e.resolveRef(bin.rhs); + self.e.mapRef(c.LLVMBuildOr(self.e.builder, lhs, rhs, "lor")); + } + + pub fn emitBoolNot(self: Ops, un: UnaryOp) void { + const operand = self.e.resolveRef(un.operand); + self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "lnot")); + } +}; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 3e20c4e..4c547d1 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -15,6 +15,7 @@ const llvm_abi = @import("../backend/llvm/abi.zig"); const llvm_debug = @import("../backend/llvm/debug.zig"); const llvm_reflection = @import("../backend/llvm/reflection.zig"); const llvm_ffi_ctors = @import("../backend/llvm/ffi_ctors.zig"); +const llvm_ops = @import("../backend/llvm/ops.zig"); const ir_inst = @import("inst.zig"); const Ref = ir_inst.Ref; const Span = ir_inst.Span; @@ -1345,224 +1346,48 @@ pub const LLVMEmitter = struct { self.debugInfo().setInstDebugLocation(instruction.span); 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); - }, - .is_comptime => { - // Compiled code is never the comptime interpreter → constant - // `false`. A `if is_comptime() { … }` branch becomes dead. - self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0)); - }, - .interp_print_frames => { - // No interpreter stack in compiled code; this only ever sits in - // a dead `is_comptime()` branch. Emit nothing. - self.advanceRefCounter(); - }, - .trace_frame => { - self.mapRef(self.reflection().emitTraceFrame(instruction)); - }, - .trace_resolve => |u| { - // The operand is a `Frame*` stamped in by `.trace_frame` (as - // i64); reinterpret and load it. - const raw = self.resolveRef(u.operand); - const frame_ty = self.getFrameStructType(); - const ptr = c.LLVMBuildIntToPtr(self.builder, raw, self.cached_ptr, "frame.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, frame_ty, ptr, "frame.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); - } - }, - .const_type => |tid| { - // Type values are Any-shaped pairs: - // { tag = .any.index() (the meta-marker), - // value = tid.index() } - // Lets storage in Any slots, struct fields, - // `Type`-typed vars, and slice elements all round- - // trip through the standard Any infrastructure. - // `case type:` in `any_to_string` matches on - // tag == `.any.index()`. Runtime `type_name(t)` - // extracts the value field and indexes into the - // type-name lookup table. - const any_ty = self.getAnyStructType(); - const tag = c.LLVMConstInt(self.cached_i64, TypeId.any.index(), 0); - const val = c.LLVMConstInt(self.cached_i64, tid.index(), 0); - var result = c.LLVMGetUndef(any_ty); - result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ct.tag"); - result = c.LLVMBuildInsertValue(self.builder, result, val, 1, "ct.val"); - self.mapRef(result); - }, + .const_int => |val| self.ops().emitConstInt(instruction, val), + .const_float => |val| self.ops().emitConstFloat(instruction, val), + .const_bool => |val| self.ops().emitConstBool(val), + .is_comptime => self.ops().emitIsComptime(), + .interp_print_frames => self.ops().emitInterpPrintFrames(), + .trace_frame => self.ops().emitTraceFrame(instruction), + .trace_resolve => |u| self.ops().emitTraceResolve(u), + .const_string => |str_id| self.ops().emitConstString(str_id), + .const_null => self.ops().emitConstNull(instruction), + .const_undef => self.ops().emitConstUndef(instruction), + .const_type => |tid| self.ops().emitConstType(tid), // ── 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); - }, + .add => |bin| self.ops().emitAdd(instruction, bin), + .sub => |bin| self.ops().emitSub(instruction, bin), + .mul => |bin| self.ops().emitMul(instruction, bin), + .div => |bin| self.ops().emitDiv(instruction, bin), + .mod => |bin| self.ops().emitMod(instruction, bin), + .neg => |un| self.ops().emitNeg(instruction, un), // ── 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); - }, + .bit_and => |bin| self.ops().emitBitAnd(instruction, bin), + .bit_or => |bin| self.ops().emitBitOr(instruction, bin), + .bit_xor => |bin| self.ops().emitBitXor(instruction, bin), + .bit_not => |un| self.ops().emitBitNot(un), + .shl => |bin| self.ops().emitShl(instruction, bin), + .shr => |bin| self.ops().emitShr(instruction, bin), // ── 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), + .cmp_eq => |bin| self.ops().emitCmpEq(instruction, bin), + .cmp_ne => |bin| self.ops().emitCmpNe(instruction, bin), + .cmp_lt => |bin| self.ops().emitCmpLt(instruction, bin), + .cmp_le => |bin| self.ops().emitCmpLe(instruction, bin), + .cmp_gt => |bin| self.ops().emitCmpGt(instruction, bin), + .cmp_ge => |bin| self.ops().emitCmpGe(instruction, bin), + .str_eq => |bin| self.ops().emitStrEq(bin), + .str_ne => |bin| self.ops().emitStrNe(bin), // ── 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")); - }, + .bool_and => |bin| self.ops().emitBoolAnd(bin), + .bool_or => |bin| self.ops().emitBoolOr(bin), + .bool_not => |un| self.ops().emitBoolNot(un), // ── Memory ──────────────────────────────────────────── .alloca => |elem_ty| { @@ -3153,16 +2978,16 @@ pub const LLVMEmitter = struct { // ── Ref tracking ──────────────────────────────────────────────── - fn mapRef(self: *LLVMEmitter, val: c.LLVMValueRef) void { + pub 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 { + pub fn advanceRefCounter(self: *LLVMEmitter) void { self.ref_counter += 1; } - fn resolveRef(self: *LLVMEmitter, ref: Ref) c.LLVMValueRef { + pub fn resolveRef(self: *LLVMEmitter, ref: Ref) c.LLVMValueRef { if (ref.isNone()) { return c.LLVMGetUndef(self.cached_i64); } @@ -3276,7 +3101,7 @@ pub const LLVMEmitter = struct { // ── Comparison helpers ──────────────────────────────────────────── - fn emitCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, _: TypeId, int_pred: c_uint, float_pred: c_uint) void { + pub 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 @@ -3358,7 +3183,7 @@ pub const LLVMEmitter = struct { 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 { + pub 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); @@ -3391,7 +3216,7 @@ pub const LLVMEmitter = struct { } /// String comparison via memcmp: compare length first, then content. - fn emitStrCmp(self: *LLVMEmitter, bin: ir_inst.BinOp, is_eq: bool) void { + pub 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; @@ -3905,7 +3730,7 @@ pub const LLVMEmitter = struct { /// 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 { + pub 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); @@ -3925,7 +3750,7 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } - fn reflection(self: *LLVMEmitter) llvm_reflection.Reflection { + pub fn reflection(self: *LLVMEmitter) llvm_reflection.Reflection { return .{ .e = self }; } @@ -3933,6 +3758,10 @@ pub const LLVMEmitter = struct { return .{ .e = self }; } + fn ops(self: *LLVMEmitter) llvm_ops.Ops { + return .{ .e = self }; + } + /// IR-type → LLVM-type lowering lives in `backend/llvm/types.zig` /// (`TypeLowering`). This stays the facade entry point (~97 callers). pub fn toLLVMType(self: *LLVMEmitter, ty: TypeId) c.LLVMTypeRef { @@ -4057,7 +3886,7 @@ pub const LLVMEmitter = struct { return c.LLVMConstArray(elem_ty, vals.ptr, n); } - fn emitStringConstant(self: *LLVMEmitter, str: []const u8) c.LLVMValueRef { + pub 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); @@ -4393,7 +4222,7 @@ fn isFloatType(ty: TypeId) bool { } /// Check if a TypeId is a float type, including float vectors. -fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { +pub fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { if (ty == .f32 or ty == .f64) return true; if (!ty.isBuiltin()) { const info = types.get(ty); @@ -4402,7 +4231,7 @@ fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool { return false; } -fn isSignedType(ty: TypeId) bool { +pub fn isSignedType(ty: TypeId) bool { return switch (ty) { .s8, .s16, .s32, .s64, .isize => true, else => false,