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 ObjcMsgSend = ir_inst.ObjcMsgSend;
|
||||||
const JniMsgSend = ir_inst.JniMsgSend;
|
const JniMsgSend = ir_inst.JniMsgSend;
|
||||||
const BuiltinCall = ir_inst.BuiltinCall;
|
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 TypeId = ir_types.TypeId;
|
||||||
const StringId = ir_types.StringId;
|
const StringId = ir_types.StringId;
|
||||||
|
|
||||||
/// Instruction-emission handlers for `emitInst`: the constant, arithmetic,
|
/// Instruction-emission handlers for `emitInst`: every opcode group — the
|
||||||
/// bitwise, comparison, logical, memory, globals, conversion, pointer, and
|
/// constant, arithmetic, bitwise, comparison, logical, memory, globals,
|
||||||
/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler,
|
/// conversion, pointer, and call opcodes (direct/indirect/objc/jni dispatch
|
||||||
/// and closure calls), plus the aggregate ops (struct, enum, union,
|
/// plus builtin, compiler, and closure calls), the aggregate ops (struct,
|
||||||
/// array/slice, tuple, and optional). A backend `*LLVMEmitter` facade
|
/// enum, union, array/slice, tuple, and optional), and the terminators,
|
||||||
/// (field `e`): each method emits one opcode's LLVM IR via `self.e.*`. The
|
/// box/unbox-Any, reflection, switch-branch, closure-creation, vector,
|
||||||
/// shared infra these bodies call back into (`mapRef`/`resolveRef`/
|
/// block-param, and misc ops. A backend `*LLVMEmitter` facade (field `e`):
|
||||||
/// `matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/`emitStrCmp`/
|
/// 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`/
|
/// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/
|
||||||
/// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/
|
/// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/
|
||||||
/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`.
|
/// `emitFailableMainRet`/`emitFieldValueGet`/`resolveAggregate`/
|
||||||
/// `emitInst`'s arms reach these via `self.ops()`.
|
/// `resolveGepStructType`) stays on `LLVMEmitter`. `emitInst`'s arms reach
|
||||||
|
/// these via `self.ops()`.
|
||||||
pub const Ops = struct {
|
pub const Ops = struct {
|
||||||
e: *LLVMEmitter,
|
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"));
|
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),
|
.call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op),
|
||||||
|
|
||||||
// ── Terminators ────────────────────────────────────────
|
// ── Terminators ────────────────────────────────────────
|
||||||
.ret => |un| {
|
.ret => |un| self.ops().emitRet(un),
|
||||||
var val = self.resolveRef(un.operand);
|
.ret_void => self.ops().emitRetVoid(),
|
||||||
const func = &self.ir_mod.functions.items[self.current_func_idx];
|
.@"unreachable" => self.ops().emitUnreachable(),
|
||||||
// Failable main: wrap the return in the entry-point reporter
|
.br => |branch| self.ops().emitBr(branch, func_idx),
|
||||||
// (ERR E4.2) — exit 0 (or the value) on success, else print the
|
.cond_br => |cbr| self.ops().emitCondBr(cbr, func_idx),
|
||||||
// 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();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Struct ops ────────────────────────────────────────────
|
// ── Struct ops ────────────────────────────────────────────
|
||||||
.struct_init => |agg| self.ops().emitStructInit(instruction, agg),
|
.struct_init => |agg| self.ops().emitStructInit(instruction, agg),
|
||||||
@@ -1549,142 +1461,30 @@ pub const LLVMEmitter = struct {
|
|||||||
.optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin),
|
.optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin),
|
||||||
|
|
||||||
// ── Box/Unbox Any ────────────────────────────────────────
|
// ── Box/Unbox Any ────────────────────────────────────────
|
||||||
.box_any => |ba| {
|
.box_any => |ba| self.ops().emitBoxAny(ba),
|
||||||
const val = self.resolveRef(ba.operand);
|
.unbox_any => |un| self.ops().emitUnboxAny(instruction, un),
|
||||||
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)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Reflection ops ──────────────────────────────────────
|
// ── Reflection ops ──────────────────────────────────────
|
||||||
.field_name_get => |fr| {
|
.field_name_get => |fr| self.ops().emitFieldNameGet(fr),
|
||||||
// Build global string array for this struct's field names, then GEP at runtime index
|
.field_value_get => |fr| self.ops().emitFieldValueGet(fr, func_idx),
|
||||||
const global = self.reflection().getOrBuildFieldNameArray(fr.struct_type);
|
.error_tag_name_get => |u| self.ops().emitErrorTagNameGet(u),
|
||||||
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);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Switch branch ────────────────────────────────────────
|
// ── Switch branch ────────────────────────────────────────
|
||||||
.switch_br => |sw| {
|
.switch_br => |sw| self.ops().emitSwitchBr(sw, func_idx),
|
||||||
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();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Closure creation ─────────────────────────────────────
|
// ── Closure creation ─────────────────────────────────────
|
||||||
.closure_create => |cc| {
|
.closure_create => |cc| self.ops().emitClosureCreate(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);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Vector ops ───────────────────────────────────────────
|
// ── Vector ops ───────────────────────────────────────────
|
||||||
.vec_splat => |un| {
|
.vec_splat => |un| self.ops().emitVecSplat(instruction, un),
|
||||||
const scalar = self.resolveRef(un.operand);
|
.vec_extract => |bin| self.ops().emitVecExtract(bin),
|
||||||
const vec_ty = self.toLLVMType(instruction.ty);
|
.vec_insert => |tri| self.ops().emitVecInsert(tri),
|
||||||
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"));
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Block params ─────────────────────────────────────────
|
// ── Block params ─────────────────────────────────────────
|
||||||
.block_param => |bp| {
|
.block_param => |bp| self.ops().emitBlockParam(instruction, 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);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Misc ─────────────────────────────────────────────────
|
// ── Misc ─────────────────────────────────────────────────
|
||||||
.placeholder => {
|
.placeholder => self.ops().emitPlaceholder(instruction),
|
||||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1706,7 +1506,7 @@ pub const LLVMEmitter = struct {
|
|||||||
return self.ref_map.get(ref.index()) orelse c.LLVMGetUndef(self.cached_i64);
|
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());
|
const key = makeBlockKey(func_idx, block_id.index());
|
||||||
return self.block_map.get(key) orelse {
|
return self.block_map.get(key) orelse {
|
||||||
std.debug.print("getBlock: missing block func={d} block={d}\n", .{ func_idx, block_id.index() });
|
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 ──────────────────────────────────────
|
// ── Value coercion helpers ──────────────────────────────────────
|
||||||
|
|
||||||
/// Coerce any scalar value to i64 for boxing into Any.
|
/// 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 ty = c.LLVMTypeOf(val);
|
||||||
const kind = c.LLVMGetTypeKind(ty);
|
const kind = c.LLVMGetTypeKind(ty);
|
||||||
if (kind == c.LLVMVoidTypeKind) {
|
if (kind == c.LLVMVoidTypeKind) {
|
||||||
@@ -2214,7 +2014,7 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Coerce signed integer to i64 using sign-extension.
|
/// 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 ty = c.LLVMTypeOf(val);
|
||||||
const kind = c.LLVMGetTypeKind(ty);
|
const kind = c.LLVMGetTypeKind(ty);
|
||||||
if (kind == c.LLVMIntegerTypeKind) {
|
if (kind == c.LLVMIntegerTypeKind) {
|
||||||
@@ -2229,7 +2029,7 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a TypeId represents a signed integer type (including arbitrary-width).
|
/// 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 (isSignedType(ty)) return true;
|
||||||
if (!ty.isBuiltin()) {
|
if (!ty.isBuiltin()) {
|
||||||
const info = self.ir_mod.types.get(ty);
|
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.
|
/// which also uses TypeId indices for type-switch comparisons.
|
||||||
/// For arbitrary-width ints (user-defined signed/unsigned), map to the closest
|
/// For arbitrary-width ints (user-defined signed/unsigned), map to the closest
|
||||||
/// builtin TypeId so the "case int:" branch matches correctly.
|
/// 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();
|
if (ty.isBuiltin()) return ty.index();
|
||||||
// For user-defined types, check if they're arbitrary-width ints
|
// For user-defined types, check if they're arbitrary-width ints
|
||||||
const info = self.ir_mod.types.get(ty);
|
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.
|
/// 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);
|
const kind = c.LLVMGetTypeKind(target);
|
||||||
if (kind == c.LLVMPointerTypeKind) {
|
if (kind == c.LLVMPointerTypeKind) {
|
||||||
return c.LLVMBuildIntToPtr(self.builder, val, target, "i2p");
|
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
|
/// name from the always-linked tag-name table, hand it + the tag to
|
||||||
/// `sx_trace_report_unhandled` (prints the header + return trace to stderr),
|
/// `sx_trace_report_unhandled` (prints the header + return trace to stderr),
|
||||||
/// and `ret i32 1`.
|
/// 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 llvm_func = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
|
||||||
const tag_i32 = self.coerceArg(tag_val, self.cached_i32);
|
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.
|
/// 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 base_val = self.resolveRef(fr.base);
|
||||||
const idx_val = self.resolveRef(fr.index);
|
const idx_val = self.resolveRef(fr.index);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user