refactor(backend): extract scalar instruction handlers into ops.zig (A7.4 slice a)

Move the Constants/Arithmetic/Bitwise/Comparisons/Logical opcode handler
bodies out of emitInst into a new Ops facade in src/backend/llvm/ops.zig.
emitInst's scalar arms now delegate via self.ops().*; the shared infra they
call (mapRef/resolveRef/matchBinOpTypes/emitCmp/emitCmpOrdered/emitStrCmp/
emitStringConstant/reflection + isFloatOrVecFloat/isSignedType) stays on
LLVMEmitter, widened to pub as needed. Pure relocation: zero snapshot churn.
This commit is contained in:
agra
2026-06-03 11:11:10 +03:00
parent 2f7c99fd11
commit 312d2e90ed
2 changed files with 339 additions and 221 deletions

289
src/backend/llvm/ops.zig Normal file
View File

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