refactor(backend): move memory/globals/conversion/pointer handlers into ops.zig (A7.4 slice b)

Relocate the `// ── Memory ──`, `// ── Globals ──`, `// ── Conversions ──`,
and `// ── Pointer ops ──` opcode handler bodies out of `emitInst` in
src/ir/emit_llvm.zig into the existing `Ops` facade in
src/backend/llvm/ops.zig. Each `emitInst` arm now delegates via
`self.ops().emit<Op>(...)`. Widen `emitConversion`, `coerceArg`, and
`getRefIRType` to `pub` (the only helpers the moved bodies call).

Pure relocation: zero snapshot churn.
This commit is contained in:
agra
2026-06-03 11:26:31 +03:00
parent fb19cf9e83
commit b4faefa607
2 changed files with 187 additions and 149 deletions

View File

@@ -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"));
}
};

View File

@@ -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)