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