refactor(backend): drain remaining emitInst handlers into ops.zig (A7.4 slice e)

Move the final inline emitInst handler groups (terminators, box/unbox-Any,
reflection, switch-branch, closure-creation, vector, block-param, misc) into
the Ops facade in src/backend/llvm/ops.zig. emitInst is now pure dispatch:
every arm delegates to self.ops().*, leaving only setInstDebugLocation plus
one-line delegations.

Widen the shared infra the moved bodies reach (emitFailableMainRet, getBlock,
anyTag, isSignedTypeEx, coerceToI64/coerceToI64Signed/coerceFromI64,
emitFieldValueGet) to pub on LLVMEmitter; helper and ref-tracking sections
stay put. Pure relocation: emitted LLVM IR byte-identical, zero snapshot churn.
This commit is contained in:
agra
2026-06-03 12:41:39 +03:00
parent 3152abcb57
commit 0e7bae563a
2 changed files with 288 additions and 235 deletions

View File

@@ -25,21 +25,32 @@ const CallIndirect = ir_inst.CallIndirect;
const ObjcMsgSend = ir_inst.ObjcMsgSend;
const JniMsgSend = ir_inst.JniMsgSend;
const BuiltinCall = ir_inst.BuiltinCall;
const TriOp = ir_inst.TriOp;
const Branch = ir_inst.Branch;
const CondBranch = ir_inst.CondBranch;
const SwitchBranch = ir_inst.SwitchBranch;
const BoxAny = ir_inst.BoxAny;
const ClosureCreate = ir_inst.ClosureCreate;
const BlockParam = ir_inst.BlockParam;
const FieldReflect = ir_inst.FieldReflect;
const TypeId = ir_types.TypeId;
const StringId = ir_types.StringId;
/// Instruction-emission handlers for `emitInst`: the constant, arithmetic,
/// bitwise, comparison, logical, memory, globals, conversion, pointer, and
/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler,
/// and closure calls), plus the aggregate ops (struct, enum, union,
/// array/slice, tuple, and optional). 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`/
/// Instruction-emission handlers for `emitInst`: every opcode group — the
/// constant, arithmetic, bitwise, comparison, logical, memory, globals,
/// conversion, pointer, and call opcodes (direct/indirect/objc/jni dispatch
/// plus builtin, compiler, and closure calls), the aggregate ops (struct,
/// enum, union, array/slice, tuple, and optional), and the terminators,
/// box/unbox-Any, reflection, switch-branch, closure-creation, vector,
/// block-param, and misc ops. 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`/`advanceRefCounter`/
/// `getBlock`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/
/// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/
/// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/
/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`.
/// `emitInst`'s arms reach these via `self.ops()`.
/// `emitFailableMainRet`/`emitFieldValueGet`/`resolveAggregate`/
/// `resolveGepStructType`) stays on `LLVMEmitter`. `emitInst`'s arms reach
/// these via `self.ops()`.
pub const Ops = struct {
e: *LLVMEmitter,
@@ -1689,4 +1700,246 @@ pub const Ops = struct {
self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel"));
}
}
// ── Terminators ────────────────────────────────────────
pub fn emitRet(self: Ops, un: UnaryOp) void {
var val = self.e.resolveRef(un.operand);
const func = &self.e.ir_mod.functions.items[self.e.current_func_idx];
// Failable main: wrap the return in the entry-point reporter
// (ERR E4.2) — exit 0 (or the value) on success, else print the
// trace + tag to stderr and exit 1 — instead of returning the
// tag/tuple as the raw exit code. Two shapes:
// `-> !` → `val` is the bare u32 error tag.
// `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both.
if (self.e.current_func_is_main) {
const rinfo = self.e.ir_mod.types.get(func.ret);
if (rinfo == .error_set) {
self.e.emitFailableMainRet(null, val);
self.e.advanceRefCounter();
return;
}
if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and
self.e.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set)
{
const value = c.LLVMBuildExtractValue(self.e.builder, val, 0, "main.ret.val");
const tag = c.LLVMBuildExtractValue(self.e.builder, val, 1, "main.ret.tag");
self.e.emitFailableMainRet(value, tag);
self.e.advanceRefCounter();
return;
}
}
// sret-shaped function: declared return-type-in-IR is
// the struct, but the LLVM signature is void with a
// prepended ptr sret param. Store the value through
// the sret slot and emit ret void.
const needs_c_abi = func.is_extern or func.call_conv == .c;
const raw_ret = self.e.toLLVMType(func.ret);
if (needs_c_abi and self.e.needsByval(func.ret, raw_ret)) {
const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder));
const sret_ptr = c.LLVMGetParam(llvm_func2, 0);
_ = c.LLVMBuildStore(self.e.builder, val, sret_ptr);
_ = c.LLVMBuildRetVoid(self.e.builder);
self.e.advanceRefCounter();
return;
}
// Coerce return value to match the function's LLVM return type
const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder));
const fn_ty = c.LLVMGlobalGetValueType(llvm_func);
const expected_ret = c.LLVMGetReturnType(fn_ty);
val = self.e.coerceArg(val, expected_ret);
// If coercion didn't fix the type (e.g. dead comptime function),
// emit undef of the correct type to avoid LLVM verification error
if (c.LLVMTypeOf(val) != expected_ret) {
val = c.LLVMGetUndef(expected_ret);
}
_ = c.LLVMBuildRet(self.e.builder, val);
self.e.advanceRefCounter();
}
pub fn emitRetVoid(self: Ops) void {
if (self.e.current_func_is_main) {
// main must return i32 0 for JIT
_ = c.LLVMBuildRet(self.e.builder, c.LLVMConstInt(self.e.cached_i32, 0, 0));
} else {
_ = c.LLVMBuildRetVoid(self.e.builder);
}
self.e.advanceRefCounter();
}
pub fn emitUnreachable(self: Ops) void {
_ = c.LLVMBuildUnreachable(self.e.builder);
self.e.advanceRefCounter();
}
pub fn emitBr(self: Ops, branch: Branch, func_idx: u32) void {
const target = self.e.getBlock(func_idx, branch.target);
_ = c.LLVMBuildBr(self.e.builder, target);
self.e.advanceRefCounter();
}
pub fn emitCondBr(self: Ops, cbr: CondBranch, func_idx: u32) void {
var cond = self.e.resolveRef(cbr.cond);
const then_bb = self.e.getBlock(func_idx, cbr.then_target);
const else_bb = self.e.getBlock(func_idx, cbr.else_target);
// Coerce condition to i1 if needed (e.g., loaded bool stored as i64)
const cond_ty = c.LLVMTypeOf(cond);
const cond_kind = c.LLVMGetTypeKind(cond_ty);
if (cond_ty != self.e.cached_i1) {
if (cond_kind == c.LLVMPointerTypeKind) {
cond = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool");
} else if (cond_kind == c.LLVMIntegerTypeKind) {
cond = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cond, c.LLVMConstInt(cond_ty, 0, 0), "tobool");
} else if (cond_kind == c.LLVMStructTypeKind) {
// Struct values are always truthy
cond = c.LLVMConstInt(self.e.cached_i1, 1, 0);
} else {
cond = c.LLVMConstInt(self.e.cached_i1, 1, 0); // default truthy
}
}
_ = c.LLVMBuildCondBr(self.e.builder, cond, then_bb, else_bb);
self.e.advanceRefCounter();
}
// ── Box/Unbox Any ──────────────────────────────────────
pub fn emitBoxAny(self: Ops, ba: BoxAny) void {
const val = self.e.resolveRef(ba.operand);
const any_ty = self.e.getAnyStructType();
// Any = { type_tag: i64, value: i64 }
const tag = c.LLVMConstInt(self.e.cached_i64, self.e.anyTag(ba.source_type), 0);
// Bitcast value to i64, using SExt for signed types, ZExt otherwise
const is_signed = self.e.isSignedTypeEx(ba.source_type);
const val_as_i64 = if (is_signed) self.e.coerceToI64Signed(val) else self.e.coerceToI64(val);
var result = c.LLVMGetUndef(any_ty);
result = c.LLVMBuildInsertValue(self.e.builder, result, tag, 0, "ba.tag");
result = c.LLVMBuildInsertValue(self.e.builder, result, val_as_i64, 1, "ba.val");
self.e.mapRef(result);
}
pub fn emitUnboxAny(self: Ops, instruction: *const Inst, un: UnaryOp) void {
const any_val = self.e.resolveRef(un.operand);
const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val));
if (any_kind == c.LLVMStructTypeKind) {
const raw = c.LLVMBuildExtractValue(self.e.builder, any_val, 1, "ua.raw");
const target_ty = self.e.toLLVMType(instruction.ty);
self.e.mapRef(self.e.coerceFromI64(raw, target_ty));
} else {
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
}
}
// ── Reflection ops ─────────────────────────────────────
pub fn emitFieldNameGet(self: Ops, fr: FieldReflect) void {
// Build global string array for this struct's field names, then GEP at runtime index
const global = self.e.reflection().getOrBuildFieldNameArray(fr.struct_type);
const idx = self.e.resolveRef(fr.index);
const string_ty = self.e.getStringStructType();
// Get struct field count for array type
const field_info = self.e.ir_mod.types.get(fr.struct_type);
const field_count: u32 = switch (field_info) {
.@"struct" => |s| @intCast(s.fields.len),
.@"union" => |u| @intCast(u.fields.len),
.tagged_union => |u| @intCast(u.fields.len),
.@"enum" => |e| @intCast(e.variants.len),
else => 0,
};
const array_ty = c.LLVMArrayType(string_ty, field_count);
const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0);
var indices = [2]c.LLVMValueRef{ zero, idx };
const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "fn.gep");
const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "fn.load");
self.e.mapRef(result);
}
pub fn emitFieldValueGet(self: Ops, fr: FieldReflect, func_idx: u32) void {
// Switch on index, each case: extractvalue field k → box as Any
self.e.emitFieldValueGet(fr, func_idx);
}
pub fn emitErrorTagNameGet(self: Ops, u: UnaryOp) void {
// Tag id → name: GEP into the always-linked tag-name table at
// the runtime tag id (the error-set value, a u32). Out-of-range
// ids can't occur — ids come from the same registry the table
// is built from — so no bounds branch is needed.
const global = self.e.reflection().getOrBuildTagNameArray();
const tag_raw = self.e.resolveRef(u.operand);
const idx = c.LLVMBuildZExt(self.e.builder, tag_raw, self.e.cached_i64, "etn.idx");
const string_ty = self.e.getStringStructType();
const n: u32 = @intCast(self.e.ir_mod.types.tags.names.items.len);
const array_ty = c.LLVMArrayType(string_ty, n);
const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0);
var indices = [2]c.LLVMValueRef{ zero, idx };
const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, array_ty, global, &indices, 2, "etn.gep");
const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "etn.load");
self.e.mapRef(result);
}
// ── Switch branch ──────────────────────────────────────
pub fn emitSwitchBr(self: Ops, sw: SwitchBranch, func_idx: u32) void {
const operand = self.e.resolveRef(sw.operand);
const default_bb = self.e.getBlock(func_idx, sw.default);
const switch_inst = c.LLVMBuildSwitch(self.e.builder, operand, default_bb, @intCast(sw.cases.len));
for (sw.cases) |case| {
const case_val = c.LLVMConstInt(c.LLVMTypeOf(operand), @bitCast(case.value), 0);
const case_bb = self.e.getBlock(func_idx, case.target);
c.LLVMAddCase(switch_inst, case_val, case_bb);
}
self.e.advanceRefCounter();
}
// ── Closure creation ───────────────────────────────────
pub fn emitClosureCreate(self: Ops, cc: ClosureCreate) void {
const fn_val = self.e.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.e.cached_ptr);
const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.e.cached_ptr) else self.e.resolveRef(cc.env);
const closure_ty = self.e.getClosureStructType();
var result = c.LLVMGetUndef(closure_ty);
result = c.LLVMBuildInsertValue(self.e.builder, result, fn_val, 0, "cc.fn");
result = c.LLVMBuildInsertValue(self.e.builder, result, env_val, 1, "cc.env");
self.e.mapRef(result);
}
// ── Vector ops ─────────────────────────────────────────
pub fn emitVecSplat(self: Ops, instruction: *const Inst, un: UnaryOp) void {
const scalar = self.e.resolveRef(un.operand);
const vec_ty = self.e.toLLVMType(instruction.ty);
const vec_len = c.LLVMGetVectorSize(vec_ty);
// Build a splat: insertelement into undef for each lane
var result = c.LLVMGetUndef(vec_ty);
var i: c_uint = 0;
while (i < vec_len) : (i += 1) {
const idx_val = c.LLVMConstInt(self.e.cached_i32, i, 0);
result = c.LLVMBuildInsertElement(self.e.builder, result, scalar, idx_val, "splat");
}
self.e.mapRef(result);
}
pub fn emitVecExtract(self: Ops, bin: BinOp) void {
const vec = self.e.resolveRef(bin.lhs);
const idx = self.e.resolveRef(bin.rhs);
self.e.mapRef(c.LLVMBuildExtractElement(self.e.builder, vec, idx, "vext"));
}
pub fn emitVecInsert(self: Ops, tri: TriOp) void {
const vec = self.e.resolveRef(tri.a);
const idx = self.e.resolveRef(tri.b);
const val = self.e.resolveRef(tri.c);
self.e.mapRef(c.LLVMBuildInsertElement(self.e.builder, vec, val, idx, "vins"));
}
// ── Block params ───────────────────────────────────────
pub fn emitBlockParam(self: Ops, instruction: *const Inst, bp: BlockParam) void {
// Create a PHI node — incoming values are filled in by fixupPhiNodes
const ty = self.e.toLLVMType(instruction.ty);
const phi = c.LLVMBuildPhi(self.e.builder, ty, "bp");
self.e.pending_phis.append(self.e.alloc, .{
.phi = phi,
.block_id = bp.block,
.param_index = bp.param_index,
}) catch unreachable;
self.e.mapRef(phi);
}
// ── Misc ───────────────────────────────────────────────
pub fn emitPlaceholder(self: Ops, instruction: *const Inst) void {
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
}
};

View File

@@ -1417,99 +1417,11 @@ pub const LLVMEmitter = struct {
.call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op),
// ── Terminators ────────────────────────────────────────
.ret => |un| {
var val = self.resolveRef(un.operand);
const func = &self.ir_mod.functions.items[self.current_func_idx];
// Failable main: wrap the return in the entry-point reporter
// (ERR E4.2) — exit 0 (or the value) on success, else print the
// trace + tag to stderr and exit 1 — instead of returning the
// tag/tuple as the raw exit code. Two shapes:
// `-> !` → `val` is the bare u32 error tag.
// `-> (int, !)` → `val` is a `{value, tag}` tuple; extract both.
if (self.current_func_is_main) {
const rinfo = self.ir_mod.types.get(func.ret);
if (rinfo == .error_set) {
self.emitFailableMainRet(null, val);
self.advanceRefCounter();
return;
}
if (rinfo == .tuple and rinfo.tuple.fields.len == 2 and
self.ir_mod.types.get(rinfo.tuple.fields[1]) == .error_set)
{
const value = c.LLVMBuildExtractValue(self.builder, val, 0, "main.ret.val");
const tag = c.LLVMBuildExtractValue(self.builder, val, 1, "main.ret.tag");
self.emitFailableMainRet(value, tag);
self.advanceRefCounter();
return;
}
}
// sret-shaped function: declared return-type-in-IR is
// the struct, but the LLVM signature is void with a
// prepended ptr sret param. Store the value through
// the sret slot and emit ret void.
const needs_c_abi = func.is_extern or func.call_conv == .c;
const raw_ret = self.toLLVMType(func.ret);
if (needs_c_abi and self.needsByval(func.ret, raw_ret)) {
const llvm_func2 = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
const sret_ptr = c.LLVMGetParam(llvm_func2, 0);
_ = c.LLVMBuildStore(self.builder, val, sret_ptr);
_ = c.LLVMBuildRetVoid(self.builder);
self.advanceRefCounter();
return;
}
// Coerce return value to match the function's LLVM return type
const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
const fn_ty = c.LLVMGlobalGetValueType(llvm_func);
const expected_ret = c.LLVMGetReturnType(fn_ty);
val = self.coerceArg(val, expected_ret);
// If coercion didn't fix the type (e.g. dead comptime function),
// emit undef of the correct type to avoid LLVM verification error
if (c.LLVMTypeOf(val) != expected_ret) {
val = c.LLVMGetUndef(expected_ret);
}
_ = c.LLVMBuildRet(self.builder, val);
self.advanceRefCounter();
},
.ret_void => {
if (self.current_func_is_main) {
// main must return i32 0 for JIT
_ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(self.cached_i32, 0, 0));
} else {
_ = c.LLVMBuildRetVoid(self.builder);
}
self.advanceRefCounter();
},
.@"unreachable" => {
_ = c.LLVMBuildUnreachable(self.builder);
self.advanceRefCounter();
},
.br => |branch| {
const target = self.getBlock(func_idx, branch.target);
_ = c.LLVMBuildBr(self.builder, target);
self.advanceRefCounter();
},
.cond_br => |cbr| {
var cond = self.resolveRef(cbr.cond);
const then_bb = self.getBlock(func_idx, cbr.then_target);
const else_bb = self.getBlock(func_idx, cbr.else_target);
// Coerce condition to i1 if needed (e.g., loaded bool stored as i64)
const cond_ty = c.LLVMTypeOf(cond);
const cond_kind = c.LLVMGetTypeKind(cond_ty);
if (cond_ty != self.cached_i1) {
if (cond_kind == c.LLVMPointerTypeKind) {
cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstNull(cond_ty), "tobool");
} else if (cond_kind == c.LLVMIntegerTypeKind) {
cond = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond, c.LLVMConstInt(cond_ty, 0, 0), "tobool");
} else if (cond_kind == c.LLVMStructTypeKind) {
// Struct values are always truthy
cond = c.LLVMConstInt(self.cached_i1, 1, 0);
} else {
cond = c.LLVMConstInt(self.cached_i1, 1, 0); // default truthy
}
}
_ = c.LLVMBuildCondBr(self.builder, cond, then_bb, else_bb);
self.advanceRefCounter();
},
.ret => |un| self.ops().emitRet(un),
.ret_void => self.ops().emitRetVoid(),
.@"unreachable" => self.ops().emitUnreachable(),
.br => |branch| self.ops().emitBr(branch, func_idx),
.cond_br => |cbr| self.ops().emitCondBr(cbr, func_idx),
// ── Struct ops ────────────────────────────────────────────
.struct_init => |agg| self.ops().emitStructInit(instruction, agg),
@@ -1549,142 +1461,30 @@ pub const LLVMEmitter = struct {
.optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin),
// ── Box/Unbox Any ────────────────────────────────────────
.box_any => |ba| {
const val = self.resolveRef(ba.operand);
const any_ty = self.getAnyStructType();
// Any = { type_tag: i64, value: i64 }
const tag = c.LLVMConstInt(self.cached_i64, self.anyTag(ba.source_type), 0);
// Bitcast value to i64, using SExt for signed types, ZExt otherwise
const is_signed = self.isSignedTypeEx(ba.source_type);
const val_as_i64 = if (is_signed) self.coerceToI64Signed(val) else self.coerceToI64(val);
var result = c.LLVMGetUndef(any_ty);
result = c.LLVMBuildInsertValue(self.builder, result, tag, 0, "ba.tag");
result = c.LLVMBuildInsertValue(self.builder, result, val_as_i64, 1, "ba.val");
self.mapRef(result);
},
.unbox_any => |un| {
const any_val = self.resolveRef(un.operand);
const any_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(any_val));
if (any_kind == c.LLVMStructTypeKind) {
const raw = c.LLVMBuildExtractValue(self.builder, any_val, 1, "ua.raw");
const target_ty = self.toLLVMType(instruction.ty);
self.mapRef(self.coerceFromI64(raw, target_ty));
} else {
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
}
},
.box_any => |ba| self.ops().emitBoxAny(ba),
.unbox_any => |un| self.ops().emitUnboxAny(instruction, un),
// ── Reflection ops ──────────────────────────────────────
.field_name_get => |fr| {
// Build global string array for this struct's field names, then GEP at runtime index
const global = self.reflection().getOrBuildFieldNameArray(fr.struct_type);
const idx = self.resolveRef(fr.index);
const string_ty = self.getStringStructType();
// Get struct field count for array type
const field_info = self.ir_mod.types.get(fr.struct_type);
const field_count: u32 = switch (field_info) {
.@"struct" => |s| @intCast(s.fields.len),
.@"union" => |u| @intCast(u.fields.len),
.tagged_union => |u| @intCast(u.fields.len),
.@"enum" => |e| @intCast(e.variants.len),
else => 0,
};
const array_ty = c.LLVMArrayType(string_ty, field_count);
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
var indices = [2]c.LLVMValueRef{ zero, idx };
const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "fn.gep");
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "fn.load");
self.mapRef(result);
},
.field_value_get => |fr| {
// Switch on index, each case: extractvalue field k → box as Any
self.emitFieldValueGet(fr, func_idx);
},
.error_tag_name_get => |u| {
// Tag id → name: GEP into the always-linked tag-name table at
// the runtime tag id (the error-set value, a u32). Out-of-range
// ids can't occur — ids come from the same registry the table
// is built from — so no bounds branch is needed.
const global = self.reflection().getOrBuildTagNameArray();
const tag_raw = self.resolveRef(u.operand);
const idx = c.LLVMBuildZExt(self.builder, tag_raw, self.cached_i64, "etn.idx");
const string_ty = self.getStringStructType();
const n: u32 = @intCast(self.ir_mod.types.tags.names.items.len);
const array_ty = c.LLVMArrayType(string_ty, n);
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
var indices = [2]c.LLVMValueRef{ zero, idx };
const gep = c.LLVMBuildInBoundsGEP2(self.builder, array_ty, global, &indices, 2, "etn.gep");
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "etn.load");
self.mapRef(result);
},
.field_name_get => |fr| self.ops().emitFieldNameGet(fr),
.field_value_get => |fr| self.ops().emitFieldValueGet(fr, func_idx),
.error_tag_name_get => |u| self.ops().emitErrorTagNameGet(u),
// ── Switch branch ────────────────────────────────────────
.switch_br => |sw| {
const operand = self.resolveRef(sw.operand);
const default_bb = self.getBlock(func_idx, sw.default);
const switch_inst = c.LLVMBuildSwitch(self.builder, operand, default_bb, @intCast(sw.cases.len));
for (sw.cases) |case| {
const case_val = c.LLVMConstInt(c.LLVMTypeOf(operand), @bitCast(case.value), 0);
const case_bb = self.getBlock(func_idx, case.target);
c.LLVMAddCase(switch_inst, case_val, case_bb);
}
self.advanceRefCounter();
},
.switch_br => |sw| self.ops().emitSwitchBr(sw, func_idx),
// ── Closure creation ─────────────────────────────────────
.closure_create => |cc| {
const fn_val = self.func_map.get(cc.func.index()) orelse c.LLVMGetUndef(self.cached_ptr);
const env_val = if (cc.env.isNone()) c.LLVMConstNull(self.cached_ptr) else self.resolveRef(cc.env);
const closure_ty = self.getClosureStructType();
var result = c.LLVMGetUndef(closure_ty);
result = c.LLVMBuildInsertValue(self.builder, result, fn_val, 0, "cc.fn");
result = c.LLVMBuildInsertValue(self.builder, result, env_val, 1, "cc.env");
self.mapRef(result);
},
.closure_create => |cc| self.ops().emitClosureCreate(cc),
// ── Vector ops ───────────────────────────────────────────
.vec_splat => |un| {
const scalar = self.resolveRef(un.operand);
const vec_ty = self.toLLVMType(instruction.ty);
const vec_len = c.LLVMGetVectorSize(vec_ty);
// Build a splat: insertelement into undef for each lane
var result = c.LLVMGetUndef(vec_ty);
var i: c_uint = 0;
while (i < vec_len) : (i += 1) {
const idx_val = c.LLVMConstInt(self.cached_i32, i, 0);
result = c.LLVMBuildInsertElement(self.builder, result, scalar, idx_val, "splat");
}
self.mapRef(result);
},
.vec_extract => |bin| {
const vec = self.resolveRef(bin.lhs);
const idx = self.resolveRef(bin.rhs);
self.mapRef(c.LLVMBuildExtractElement(self.builder, vec, idx, "vext"));
},
.vec_insert => |tri| {
const vec = self.resolveRef(tri.a);
const idx = self.resolveRef(tri.b);
const val = self.resolveRef(tri.c);
self.mapRef(c.LLVMBuildInsertElement(self.builder, vec, val, idx, "vins"));
},
.vec_splat => |un| self.ops().emitVecSplat(instruction, un),
.vec_extract => |bin| self.ops().emitVecExtract(bin),
.vec_insert => |tri| self.ops().emitVecInsert(tri),
// ── Block params ─────────────────────────────────────────
.block_param => |bp| {
// Create a PHI node — incoming values are filled in by fixupPhiNodes
const ty = self.toLLVMType(instruction.ty);
const phi = c.LLVMBuildPhi(self.builder, ty, "bp");
self.pending_phis.append(self.alloc, .{
.phi = phi,
.block_id = bp.block,
.param_index = bp.param_index,
}) catch unreachable;
self.mapRef(phi);
},
.block_param => |bp| self.ops().emitBlockParam(instruction, bp),
// ── Misc ─────────────────────────────────────────────────
.placeholder => {
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
},
.placeholder => self.ops().emitPlaceholder(instruction),
}
}
@@ -1706,7 +1506,7 @@ pub const LLVMEmitter = struct {
return self.ref_map.get(ref.index()) orelse c.LLVMGetUndef(self.cached_i64);
}
fn getBlock(self: *LLVMEmitter, func_idx: u32, block_id: BlockId) c.LLVMBasicBlockRef {
pub fn getBlock(self: *LLVMEmitter, func_idx: u32, block_id: BlockId) c.LLVMBasicBlockRef {
const key = makeBlockKey(func_idx, block_id.index());
return self.block_map.get(key) orelse {
std.debug.print("getBlock: missing block func={d} block={d}\n", .{ func_idx, block_id.index() });
@@ -2180,7 +1980,7 @@ pub const LLVMEmitter = struct {
// ── Value coercion helpers ──────────────────────────────────────
/// Coerce any scalar value to i64 for boxing into Any.
fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
pub fn coerceToI64(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
const ty = c.LLVMTypeOf(val);
const kind = c.LLVMGetTypeKind(ty);
if (kind == c.LLVMVoidTypeKind) {
@@ -2214,7 +2014,7 @@ pub const LLVMEmitter = struct {
}
/// Coerce signed integer to i64 using sign-extension.
fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
pub fn coerceToI64Signed(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
const ty = c.LLVMTypeOf(val);
const kind = c.LLVMGetTypeKind(ty);
if (kind == c.LLVMIntegerTypeKind) {
@@ -2229,7 +2029,7 @@ pub const LLVMEmitter = struct {
}
/// Check if a TypeId represents a signed integer type (including arbitrary-width).
fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool {
pub fn isSignedTypeEx(self: *LLVMEmitter, ty: TypeId) bool {
if (isSignedType(ty)) return true;
if (!ty.isBuiltin()) {
const info = self.ir_mod.types.get(ty);
@@ -2248,7 +2048,7 @@ pub const LLVMEmitter = struct {
/// which also uses TypeId indices for type-switch comparisons.
/// For arbitrary-width ints (user-defined signed/unsigned), map to the closest
/// builtin TypeId so the "case int:" branch matches correctly.
fn anyTag(self: *LLVMEmitter, ty: TypeId) u64 {
pub fn anyTag(self: *LLVMEmitter, ty: TypeId) u64 {
if (ty.isBuiltin()) return ty.index();
// For user-defined types, check if they're arbitrary-width ints
const info = self.ir_mod.types.get(ty);
@@ -2272,7 +2072,7 @@ pub const LLVMEmitter = struct {
}
/// Coerce i64 back to the target type for unboxing from Any.
fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef {
pub fn coerceFromI64(self: *LLVMEmitter, val: c.LLVMValueRef, target: c.LLVMTypeRef) c.LLVMValueRef {
const kind = c.LLVMGetTypeKind(target);
if (kind == c.LLVMPointerTypeKind) {
return c.LLVMBuildIntToPtr(self.builder, val, target, "i2p");
@@ -2679,7 +2479,7 @@ pub const LLVMEmitter = struct {
/// name from the always-linked tag-name table, hand it + the tag to
/// `sx_trace_report_unhandled` (prints the header + return trace to stderr),
/// and `ret i32 1`.
fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void {
pub fn emitFailableMainRet(self: *LLVMEmitter, value: ?c.LLVMValueRef, tag_val: c.LLVMValueRef) void {
const llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
const tag_i32 = self.coerceArg(tag_val, self.cached_i32);
@@ -2719,7 +2519,7 @@ pub const LLVMEmitter = struct {
}
/// Emit field_value_get: switch on runtime index, each case extracts a field and boxes it as Any.
fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void {
pub fn emitFieldValueGet(self: *LLVMEmitter, fr: ir_inst.FieldReflect, func_idx: u32) void {
const base_val = self.resolveRef(fr.base);
const idx_val = self.resolveRef(fr.index);