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:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user