Merge branch 'flow/sx-plan-arch/A7.4a' into arch-refactor

This commit is contained in:
agra
2026-06-03 11:20:45 +03:00
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"));
}
};

View File

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