diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index 1f627e2..36f66bb 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -9,16 +9,22 @@ const LLVMEmitter = emit.LLVMEmitter; const Inst = ir_inst.Inst; const BinOp = ir_inst.BinOp; const UnaryOp = ir_inst.UnaryOp; +const Store = ir_inst.Store; +const Conversion = ir_inst.Conversion; +const GlobalId = ir_inst.GlobalId; +const GlobalSet = ir_inst.GlobalSet; +const FuncId = ir_inst.FuncId; 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 +/// Instruction-emission handlers for `emitInst`: the constant, arithmetic, +/// bitwise, comparison, logical, memory, globals, conversion, and pointer +/// 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()`. +/// `emitStrCmp`/`emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ +/// `getRefIRType`) stays on `LLVMEmitter`. `emitInst`'s arms reach these via +/// `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -286,4 +292,162 @@ pub const Ops = struct { const operand = self.e.resolveRef(un.operand); self.e.mapRef(c.LLVMBuildNot(self.e.builder, operand, "lnot")); } + + // ── Memory ──────────────────────────────────────────── + pub fn emitAlloca(self: Ops, elem_ty: TypeId) void { + const llvm_ty = self.e.toLLVMType(elem_ty); + const result = c.LLVMBuildAlloca(self.e.builder, llvm_ty, "alloca"); + self.e.mapRef(result); + } + + pub fn emitLoad(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const ptr = self.e.resolveRef(un.operand); + const ptr_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(ptr)); + if (ptr_kind == c.LLVMPointerTypeKind and instruction.ty != .void) { + const llvm_ty = self.e.toLLVMType(instruction.ty); + const result = c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "load"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(if (instruction.ty == .void) .s64 else instruction.ty))); + } + } + + pub fn emitStore(self: Ops, st: Store) void { + const ptr = self.e.resolveRef(st.ptr); + var val = self.e.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.e.getRefIRType(st.ptr)) |ptr_ir_ty| { + const pointee_info = self.e.ir_mod.types.get(ptr_ir_ty); + const target_ty: ?c.LLVMTypeRef = switch (pointee_info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + else => null, + }; + if (target_ty) |tt| { + val = self.e.coerceArg(val, tt); + } + } + _ = c.LLVMBuildStore(self.e.builder, val, ptr); + } + self.e.advanceRefCounter(); + } + + // ── Globals ─────────────────────────────────────────── + pub fn emitGlobalGet(self: Ops, instruction: *const Inst, gid: GlobalId) void { + const llvm_global = self.e.global_map.get(gid.index()) orelse { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + return; + }; + const llvm_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, llvm_global, "gload")); + } + + pub fn emitGlobalAddr(self: Ops, gid: GlobalId) void { + const llvm_global = self.e.global_map.get(gid.index()) orelse { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + return; + }; + // Return the global's address directly (no load) + self.e.mapRef(llvm_global); + } + + pub fn emitFuncRef(self: Ops, fid: FuncId) void { + // Produce a reference to the function as a function pointer value + if (self.e.func_map.get(@intFromEnum(fid))) |llvm_func| { + self.e.mapRef(llvm_func); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitGlobalSet(self: Ops, gs: GlobalSet) void { + const llvm_global = self.e.global_map.get(gs.global.index()) orelse { + self.e.advanceRefCounter(); + return; + }; + const val = self.e.resolveRef(gs.value); + _ = c.LLVMBuildStore(self.e.builder, val, llvm_global); + self.e.advanceRefCounter(); + } + + // ── Conversions ─────────────────────────────────────── + pub fn emitWiden(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = self.e.emitConversion(operand, conv.from, conv.to, to_ty); + self.e.mapRef(result); + } + + pub fn emitNarrow(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = self.e.emitConversion(operand, conv.from, conv.to, to_ty); + self.e.mapRef(result); + } + + pub fn emitBitcast(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + // LLVMBuildBitCast doesn't accept ptr↔int on modern + // LLVM. Dispatch to PtrToInt / IntToPtr when needed — + // lower.zig emits a `bitcast` IR op for both shapes. + const from_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(operand)); + const to_kind = c.LLVMGetTypeKind(to_ty); + if (from_kind == c.LLVMPointerTypeKind and to_kind == c.LLVMIntegerTypeKind) { + const i64_val = c.LLVMBuildPtrToInt(self.e.builder, operand, self.e.cached_i64, "pti"); + const w = c.LLVMGetIntTypeWidth(to_ty); + if (w == 64) { + self.e.mapRef(i64_val); + } else if (w < 64) { + self.e.mapRef(c.LLVMBuildTrunc(self.e.builder, i64_val, to_ty, "pti.tr")); + } else { + self.e.mapRef(c.LLVMBuildZExt(self.e.builder, i64_val, to_ty, "pti.ext")); + } + } else if (from_kind == c.LLVMIntegerTypeKind and to_kind == c.LLVMPointerTypeKind) { + self.e.mapRef(c.LLVMBuildIntToPtr(self.e.builder, operand, to_ty, "itp")); + } else { + self.e.mapRef(c.LLVMBuildBitCast(self.e.builder, operand, to_ty, "bitcast")); + } + } + + pub fn emitIntToFloat(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = if (emit.isSignedType(conv.from)) + c.LLVMBuildSIToFP(self.e.builder, operand, to_ty, "sitofp") + else + c.LLVMBuildUIToFP(self.e.builder, operand, to_ty, "uitofp"); + self.e.mapRef(result); + } + + pub fn emitFloatToInt(self: Ops, conv: Conversion) void { + const operand = self.e.resolveRef(conv.operand); + const to_ty = self.e.toLLVMType(conv.to); + const result = if (emit.isSignedType(conv.to)) + c.LLVMBuildFPToSI(self.e.builder, operand, to_ty, "fptosi") + else + c.LLVMBuildFPToUI(self.e.builder, operand, to_ty, "fptoui"); + self.e.mapRef(result); + } + + // ── Pointer ops ─────────────────────────────────────── + pub fn emitAddrOf(self: Ops, un: UnaryOp) void { + // addr_of returns the pointer directly (the operand is already a ptr from alloca) + self.e.mapRef(self.e.resolveRef(un.operand)); + } + + pub fn emitDeref(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const ptr = self.e.resolveRef(un.operand); + const llvm_ty = self.e.toLLVMType(instruction.ty); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "deref")); + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 4c547d1..037d396 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1390,151 +1390,25 @@ pub const LLVMEmitter = struct { .bool_not => |un| self.ops().emitBoolNot(un), // ── 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(); - }, + .alloca => |elem_ty| self.ops().emitAlloca(elem_ty), + .load => |un| self.ops().emitLoad(instruction, un), + .store => |st| self.ops().emitStore(st), // ── 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(); - }, + .global_get => |gid| self.ops().emitGlobalGet(instruction, gid), + .global_addr => |gid| self.ops().emitGlobalAddr(gid), + .func_ref => |fid| self.ops().emitFuncRef(fid), + .global_set => |gs| self.ops().emitGlobalSet(gs), // ── 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); - // LLVMBuildBitCast doesn't accept ptr↔int on modern - // LLVM. Dispatch to PtrToInt / IntToPtr when needed — - // lower.zig emits a `bitcast` IR op for both shapes. - const from_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(operand)); - const to_kind = c.LLVMGetTypeKind(to_ty); - if (from_kind == c.LLVMPointerTypeKind and to_kind == c.LLVMIntegerTypeKind) { - const i64_val = c.LLVMBuildPtrToInt(self.builder, operand, self.cached_i64, "pti"); - const w = c.LLVMGetIntTypeWidth(to_ty); - if (w == 64) { - self.mapRef(i64_val); - } else if (w < 64) { - self.mapRef(c.LLVMBuildTrunc(self.builder, i64_val, to_ty, "pti.tr")); - } else { - self.mapRef(c.LLVMBuildZExt(self.builder, i64_val, to_ty, "pti.ext")); - } - } else if (from_kind == c.LLVMIntegerTypeKind and to_kind == c.LLVMPointerTypeKind) { - self.mapRef(c.LLVMBuildIntToPtr(self.builder, operand, to_ty, "itp")); - } else { - 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); - }, + .widen => |conv| self.ops().emitWiden(conv), + .narrow => |conv| self.ops().emitNarrow(conv), + .bitcast => |conv| self.ops().emitBitcast(conv), + .int_to_float => |conv| self.ops().emitIntToFloat(conv), + .float_to_int => |conv| self.ops().emitFloatToInt(conv), // ── 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")); - }, + .addr_of => |un| self.ops().emitAddrOf(un), + .deref => |un| self.ops().emitDeref(instruction, un), // ── Calls ───────────────────────────────────────────── .objc_msg_send => |msg| { @@ -3273,7 +3147,7 @@ pub const LLVMEmitter = struct { // ── Conversion helpers ────────────────────────────────────────── - fn emitConversion(self: *LLVMEmitter, operand: c.LLVMValueRef, from: TypeId, to: TypeId, to_ty: c.LLVMTypeRef) c.LLVMValueRef { + pub 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); @@ -3588,7 +3462,7 @@ pub const LLVMEmitter = struct { /// 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 { + pub 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); @@ -3714,7 +3588,7 @@ pub const LLVMEmitter = struct { } /// Look up the IR type of a Ref in the current function (for store coercion). - fn getRefIRType(self: *LLVMEmitter, ref: Ref) ?TypeId { + pub 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)