From 1be16511ec35542d0475f0566ba3f473f8d1e734 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 12:03:45 +0300 Subject: [PATCH] refactor(backend): move aggregate handlers into ops.zig (A7.4 slice d) Relocate the struct, enum, union, array/slice, tuple, and optional opcode handler bodies out of emitInst into the existing Ops facade. Each moved arm now delegates via self.ops().emit(...); shared infra stays on LLVMEmitter, with resolveAggregate/resolveGepStructType widened to pub as the GEP handlers require. Pure relocation, behavior-preserving: zero snapshot churn (361/0). --- src/backend/llvm/ops.zig | 559 ++++++++++++++++++++++++++++++++++++++- src/ir/emit_llvm.zig | 537 ++----------------------------------- 2 files changed, 574 insertions(+), 522 deletions(-) diff --git a/src/backend/llvm/ops.zig b/src/backend/llvm/ops.zig index f5174bc..ee5e639 100644 --- a/src/backend/llvm/ops.zig +++ b/src/backend/llvm/ops.zig @@ -11,6 +11,10 @@ const Interpreter = interp_mod.Interpreter; const Inst = ir_inst.Inst; const BinOp = ir_inst.BinOp; const UnaryOp = ir_inst.UnaryOp; +const Aggregate = ir_inst.Aggregate; +const FieldAccess = ir_inst.FieldAccess; +const EnumInit = ir_inst.EnumInit; +const Subslice = ir_inst.Subslice; const Store = ir_inst.Store; const Conversion = ir_inst.Conversion; const GlobalId = ir_inst.GlobalId; @@ -27,13 +31,15 @@ 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). A backend `*LLVMEmitter` facade (field `e`): each -/// method emits one opcode's LLVM IR via `self.e.*`. The shared infra these -/// bodies call back into (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/ -/// `emitCmpOrdered`/`emitStrCmp`/`emitStringConstant`/`reflection`/ -/// `emitConversion`/`coerceArg`/`getRefIRType`/`loadJniFn`/`extractSlicePtr`/ -/// `emitJniConstructor`) stays on `LLVMEmitter`. `emitInst`'s arms reach these -/// via `self.ops()`. +/// 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`/ +/// `emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/ +/// `getRefIRType`/`loadJniFn`/`extractSlicePtr`/`emitJniConstructor`/ +/// `resolveAggregate`/`resolveGepStructType`) stays on `LLVMEmitter`. +/// `emitInst`'s arms reach these via `self.ops()`. pub const Ops = struct { e: *LLVMEmitter, @@ -1144,4 +1150,543 @@ pub const Ops = struct { self.e.advanceRefCounter(); } } + + // ── Struct ops ──────────────────────────────────────────── + pub fn emitStructInit(self: Ops, instruction: *const Inst, agg: Aggregate) void { + const struct_ty = self.e.toLLVMType(instruction.ty); + const type_kind = c.LLVMGetTypeKind(struct_ty); + // For vector types, use InsertElement instead of InsertValue + const is_vector = type_kind == c.LLVMVectorTypeKind or type_kind == c.LLVMScalableVectorTypeKind; + // For array types, get expected element type for coercion + const is_array = type_kind == c.LLVMArrayTypeKind; + const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null; + var result = c.LLVMGetUndef(struct_ty); + for (agg.fields, 0..) |field_ref, i| { + var field_val = self.e.resolveRef(field_ref); + if (is_vector) { + // Coerce element to match vector element type + const vec_elem_ty = c.LLVMGetElementType(struct_ty); + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != vec_elem_ty) { + field_val = self.e.coerceArg(field_val, vec_elem_ty); + } + const idx = c.LLVMConstInt(self.e.cached_i32, @intCast(i), 0); + result = c.LLVMBuildInsertElement(self.e.builder, result, field_val, idx, "vi"); + } else { + // Coerce element to match array element type if needed + if (elem_llvm_ty) |elt| { + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != elt) { + const val_kind = c.LLVMGetTypeKind(val_ty); + const elt_kind = c.LLVMGetTypeKind(elt); + if (val_kind == c.LLVMIntegerTypeKind and elt_kind == c.LLVMIntegerTypeKind) { + const val_w = c.LLVMGetIntTypeWidth(val_ty); + const elt_w = c.LLVMGetIntTypeWidth(elt); + if (val_w > elt_w) { + field_val = c.LLVMBuildTrunc(self.e.builder, field_val, elt, "atrunc"); + } else if (val_w < elt_w) { + field_val = c.LLVMBuildZExt(self.e.builder, field_val, elt, "aext"); + } + } + } + } else if (type_kind == c.LLVMStructTypeKind) { + // Coerce struct field value to match declared field type + const n_elts = c.LLVMCountStructElementTypes(struct_ty); + if (n_elts > 0 and i < n_elts) { + const field_ty = c.LLVMStructGetTypeAtIndex(struct_ty, @intCast(i)); + const val_ty = c.LLVMTypeOf(field_val); + if (val_ty != field_ty) { + field_val = self.e.coerceArg(field_val, field_ty); + } + } + } + result = c.LLVMBuildInsertValue(self.e.builder, result, field_val, @intCast(i), "si"); + } + } + self.e.mapRef(result); + } + + pub fn emitStructGet(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + // Safety: null base means unresolved reference — emit undef + if (base == null) { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } else { + // Safety: check that base is an aggregate type (struct/array/vector), not scalar + const base_ty = c.LLVMTypeOf(base); + const base_ty_kind = c.LLVMGetTypeKind(base_ty); + if (base_ty_kind == c.LLVMVectorTypeKind or base_ty_kind == c.LLVMScalableVectorTypeKind) { + // Vector: use ExtractElement with an index + const idx = c.LLVMConstInt(self.e.cached_i32, @intCast(fa.field_index), 0); + const result = c.LLVMBuildExtractElement(self.e.builder, base, idx, "ve"); + self.e.mapRef(result); + } else if (base_ty_kind == c.LLVMStructTypeKind or base_ty_kind == c.LLVMArrayTypeKind) { + // Validate field index is in bounds + const n_fields = if (base_ty_kind == c.LLVMStructTypeKind) c.LLVMCountStructElementTypes(base_ty) else 0; + // Check builder has valid insert point + const insert_bb = c.LLVMGetInsertBlock(self.e.builder); + if (insert_bb == null or (n_fields == 0 and base_ty_kind == c.LLVMStructTypeKind) or (n_fields > 0 and fa.field_index >= n_fields)) { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } else { + const result = c.LLVMBuildExtractValue(self.e.builder, base, @intCast(fa.field_index), "sg"); + self.e.mapRef(result); + } + } else { + // Base is not an aggregate (e.g., placeholder undef of scalar type) + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + } + + pub fn emitStructGep(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base_ptr = self.e.resolveRef(fa.base); + // Safety: verify base is a pointer before GEP + const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); + if (base_ty_kind == c.LLVMPointerTypeKind) { + const struct_llvm_ty = if (fa.base_type) |bt| + self.e.toLLVMType(self.e.resolveAggregate(bt)) + else + self.e.resolveGepStructType(fa.base, instruction); + const st_kind = c.LLVMGetTypeKind(struct_llvm_ty); + if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) { + const result = c.LLVMBuildStructGEP2(self.e.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + // ── Enum ops ───────────────────────────────────────────── + pub fn emitEnumInit(self: Ops, instruction: *const Inst, ei: EnumInit) void { + if (ei.payload.isNone()) { + // Simple enum (no payload) — just a tag integer + const ty = self.e.toLLVMType(instruction.ty); + const ty_kind = c.LLVMGetTypeKind(ty); + if (ty_kind == c.LLVMIntegerTypeKind) { + // Plain enum or builtin integer → integer constant + self.e.mapRef(c.LLVMConstInt(ty, ei.tag, 0)); + } else if (ty_kind == c.LLVMStructTypeKind) { + // Tagged union with no payload — header field 0 holds the tag + const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); + var result = c.LLVMGetUndef(ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, tag_val, 0, "ei.tag"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMConstInt(self.e.cached_i64, ei.tag, 0)); + } + } else { + // Tagged union with payload — { header, payload_bytes } + const union_ty = self.e.toLLVMType(instruction.ty); + const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0); + const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); + const payload_val = self.e.resolveRef(ei.payload); + + // alloca union, store tag, bitcast payload area, store payload + const tmp = c.LLVMBuildAlloca(self.e.builder, union_ty, "ei.tmp"); + // Store tag at field 0 + const tag_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_ty, tmp, 0, "ei.tagp"); + _ = c.LLVMBuildStore(self.e.builder, tag_val, tag_ptr); + // Store payload at field 1 (bitcast the byte array to payload type) + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_ty, tmp, 1, "ei.pp"); + const payload_typed_ptr = c.LLVMBuildBitCast(self.e.builder, payload_ptr, self.e.cached_ptr, "ei.pcast"); + _ = c.LLVMBuildStore(self.e.builder, payload_val, payload_typed_ptr); + // Load the whole union value + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, union_ty, tmp, "ei.val")); + } + } + + pub fn emitEnumTag(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + // Check if this is a plain enum (integer) or tagged union (struct with tag at 0) + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Tagged union — extract field 0 (tag) + var tag = c.LLVMBuildExtractValue(self.e.builder, val, 0, "etag"); + // Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags) + // This is essential for FFI unions where the i64 tag slot contains + // a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved) + const target_ty = self.e.toLLVMType(instruction.ty); + const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag)); + const target_bits = c.LLVMGetIntTypeWidth(target_ty); + if (target_bits < extracted_bits) { + tag = c.LLVMBuildTrunc(self.e.builder, tag, target_ty, "etag.trunc"); + } + self.e.mapRef(tag); + } else { + // Plain enum — the value IS the tag + self.e.mapRef(val); + } + } + + pub fn emitEnumPayload(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + const result_ty = self.e.toLLVMType(instruction.ty); + const base_ty = c.LLVMTypeOf(base); + const base_kind = c.LLVMGetTypeKind(base_ty); + if (base_kind == c.LLVMStructTypeKind) { + // Tagged union: alloca, store, GEP field 1 (payload area), bitcast, load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ep.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, base_ty, tmp, 1, "ep.pp"); + const typed_ptr = c.LLVMBuildBitCast(self.e.builder, payload_ptr, self.e.cached_ptr, "ep.cast"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, typed_ptr, "ep.val")); + } else { + self.e.mapRef(c.LLVMGetUndef(result_ty)); + } + } + + // ── Union ops ──────────────────────────────────────────── + pub fn emitUnionGet(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + const result_ty = self.e.toLLVMType(instruction.ty); + // Union field access: reinterpret the union's data area as the target type + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMStructTypeKind) { + // Tagged union { header, payload_bytes } — access payload at field 1 + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ug.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, base_ty, tmp, 1, "ug.pp"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, payload_ptr, "ug.val")); + } else { + // Untagged union [N x i8] — alloca, store, reinterpret-load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ug.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, result_ty, tmp, "ug.val")); + } + } + + pub fn emitUnionGep(self: Ops, instruction: *const Inst, fa: FieldAccess) void { + const base_ptr = self.e.resolveRef(fa.base); + const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); + if (base_ty_kind == c.LLVMPointerTypeKind) { + const union_llvm_ty = if (fa.base_type) |bt| + self.e.toLLVMType(self.e.resolveAggregate(bt)) + else + self.e.resolveGepStructType(fa.base, instruction); + const st_kind = c.LLVMGetTypeKind(union_llvm_ty); + if (st_kind == c.LLVMStructTypeKind) { + // Tagged union — payload is at field 1 + const payload_ptr = c.LLVMBuildStructGEP2(self.e.builder, union_llvm_ty, base_ptr, 1, "ugep.pp"); + self.e.mapRef(payload_ptr); + } else { + // Untagged union — data starts at offset 0 + self.e.mapRef(base_ptr); + } + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + // ── Array/Slice ops ─────────────────────────────────────── + pub fn emitIndexGet(self: Ops, instruction: *const Inst, bin: BinOp) void { + const base = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) { + // Vector — use extractelement + // Coerce index to i32 if needed + const idx32 = self.e.coerceArg(idx, self.e.cached_i32); + self.e.mapRef(c.LLVMBuildExtractElement(self.e.builder, base, idx32, "ve")); + } else if (kind == c.LLVMArrayTypeKind) { + // Fixed-size array value — alloca, store, GEP, load + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ig.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), idx }; + const ptr = c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else if (kind == c.LLVMPointerTypeKind) { + // Pointer (many-pointer or raw ptr) — GEP + load + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{idx}; + const ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, base, &indices, 1, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract ptr, GEP, load + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "ig.data"); + const elem_ty = self.e.toLLVMType(instruction.ty); + var indices = [_]c.LLVMValueRef{idx}; + const ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, data, &indices, 1, "ig.ptr"); + self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, elem_ty, ptr, "ig.val")); + } else { + // Non-aggregate base (lowering error) — emit undef + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + pub fn emitIndexGep(self: Ops, instruction: *const Inst, bin: BinOp) void { + const base = self.e.resolveRef(bin.lhs); + const idx = self.e.resolveRef(bin.rhs); + const base_ty = c.LLVMTypeOf(base); + const kind = c.LLVMGetTypeKind(base_ty); + if (kind == c.LLVMArrayTypeKind) { + // Fixed-size array value — alloca, store, GEP + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "igp.tmp"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), idx }; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "igp.ptr")); + } else if (kind == c.LLVMPointerTypeKind) { + // Pointer — GEP with proper element type + const gep_elem = blk: { + // instruction.ty is the result type (ptr to element) + // Resolve the pointee type for the GEP element size + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + .many_pointer => |p| self.e.toLLVMType(p.element), + else => self.e.cached_i8, // fallback + }; + }; + var indices = [_]c.LLVMValueRef{idx}; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, gep_elem, base, &indices, 1, "igp.ptr")); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract ptr, GEP with proper element type + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "igp.data"); + const gep_elem = blk: { + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .pointer => |p| self.e.toLLVMType(p.pointee), + .many_pointer => |p| self.e.toLLVMType(p.element), + else => self.e.cached_i8, + }; + }; + var indices = [_]c.LLVMValueRef{idx}; + self.e.mapRef(c.LLVMBuildGEP2(self.e.builder, gep_elem, data, &indices, 1, "igp.ptr")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitLength(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMArrayTypeKind) { + const len = c.LLVMGetArrayLength2(val_ty); + self.e.mapRef(c.LLVMConstInt(self.e.cached_i64, len, 0)); + } else if (kind == c.LLVMStructTypeKind) { + // Slice/string {ptr, len} — extract field 1 (len) + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 1, "len")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_i64)); + } + } + + pub fn emitDataPtr(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 0, "dptr")); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.cached_ptr)); + } + } + + pub fn emitSubslice(self: Ops, instruction: *const Inst, ss: Subslice) void { + const base = self.e.resolveRef(ss.base); + var lo = self.e.resolveRef(ss.lo); + var hi = self.e.resolveRef(ss.hi); + // Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned) + if (c.LLVMTypeOf(lo) != self.e.cached_i64) { + lo = c.LLVMBuildZExt(self.e.builder, lo, self.e.cached_i64, "ss.lo64"); + } + if (c.LLVMTypeOf(hi) != self.e.cached_i64) { + hi = c.LLVMBuildZExt(self.e.builder, hi, self.e.cached_i64, "ss.hi64"); + } + const base_ty = c.LLVMTypeOf(base); + const base_kind = c.LLVMGetTypeKind(base_ty); + const slice_ty = self.e.toLLVMType(instruction.ty); + // Resolve element type from the result slice type for correct GEP stride + const elem_ty = blk: { + const info = self.e.ir_mod.types.get(instruction.ty); + break :blk switch (info) { + .slice => |s| self.e.toLLVMType(s.element), + else => self.e.cached_i8, + }; + }; + if (base_kind == c.LLVMStructTypeKind) { + // Slice/string: extract data ptr, GEP by lo + const data = c.LLVMBuildExtractValue(self.e.builder, base, 0, "ss.data"); + var lo_indices = [_]c.LLVMValueRef{lo}; + const new_ptr = c.LLVMBuildGEP2(self.e.builder, elem_ty, data, &lo_indices, 1, "ss.ptr"); + var new_len = c.LLVMBuildSub(self.e.builder, hi, lo, "ss.len"); + // Ensure length is i64 for slice struct {ptr, i64} + if (c.LLVMTypeOf(new_len) != self.e.cached_i64) { + new_len = c.LLVMBuildSExt(self.e.builder, new_len, self.e.cached_i64, "ss.ext"); + } + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_ptr, 0, "ss.wptr"); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_len, 1, "ss.wlen"); + self.e.mapRef(result); + } else if (base_kind == c.LLVMArrayTypeKind) { + // Array: alloca, GEP to element at lo, compute len + const tmp = c.LLVMBuildAlloca(self.e.builder, base_ty, "ss.arr"); + _ = c.LLVMBuildStore(self.e.builder, base, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), lo }; + const new_ptr = c.LLVMBuildGEP2(self.e.builder, base_ty, tmp, &indices, 2, "ss.ptr"); + var new_len = c.LLVMBuildSub(self.e.builder, hi, lo, "ss.len"); + // Ensure length is i64 for slice struct {ptr, i64} + if (c.LLVMTypeOf(new_len) != self.e.cached_i64) { + new_len = c.LLVMBuildSExt(self.e.builder, new_len, self.e.cached_i64, "ss.ext"); + } + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_ptr, 0, "ss.wptr"); + result = c.LLVMBuildInsertValue(self.e.builder, result, new_len, 1, "ss.wlen"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(slice_ty)); + } + } + + pub fn emitArrayToSlice(self: Ops, instruction: *const Inst, un: UnaryOp) void { + const arr = self.e.resolveRef(un.operand); + const arr_ty = c.LLVMTypeOf(arr); + const arr_kind = c.LLVMGetTypeKind(arr_ty); + if (arr_kind == c.LLVMArrayTypeKind) { + const len = c.LLVMGetArrayLength2(arr_ty); + const tmp = c.LLVMBuildAlloca(self.e.builder, arr_ty, "a2s.tmp"); + _ = c.LLVMBuildStore(self.e.builder, arr, tmp); + var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.e.cached_i64, 0, 0), c.LLVMConstInt(self.e.cached_i64, 0, 0) }; + const elem_ptr = c.LLVMBuildGEP2(self.e.builder, arr_ty, tmp, &indices, 2, "a2s.ptr"); + const slice_ty = self.e.toLLVMType(instruction.ty); + var result = c.LLVMGetUndef(slice_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, elem_ptr, 0, "a2s.wptr"); + const len_val = c.LLVMConstInt(self.e.cached_i64, len, 0); + result = c.LLVMBuildInsertValue(self.e.builder, result, len_val, 1, "a2s.wlen"); + self.e.mapRef(result); + } else { + self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty))); + } + } + + // ── Tuple ops ──────────────────────────────────────────── + pub fn emitTupleInit(self: Ops, instruction: *const Inst, agg: Aggregate) void { + const tuple_ty = self.e.toLLVMType(instruction.ty); + var result = c.LLVMGetUndef(tuple_ty); + for (agg.fields, 0..) |field_ref, i| { + const field_val = self.e.resolveRef(field_ref); + result = c.LLVMBuildInsertValue(self.e.builder, result, field_val, @intCast(i), "ti"); + } + self.e.mapRef(result); + } + + pub fn emitTupleGet(self: Ops, fa: FieldAccess) void { + const base = self.e.resolveRef(fa.base); + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, base, @intCast(fa.field_index), "tg")); + } + + // ── Optional ops ───────────────────────────────────────── + pub fn emitOptionalWrap(self: Ops, instruction: *const Inst, un: UnaryOp) void { + var val = self.e.resolveRef(un.operand); + const opt_ty = self.e.toLLVMType(instruction.ty); + const opt_kind = c.LLVMGetTypeKind(opt_ty); + if (opt_kind == c.LLVMPointerTypeKind) { + // ?*T — pointer is the optional itself (null = none) + self.e.mapRef(val); + } else if (opt_kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(opt_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(opt_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // ?T → { T, i1 } — wrap value + true flag + const inner_ty = c.LLVMStructGetTypeAtIndex(opt_ty, 0); + val = self.e.coerceArg(val, inner_ty); + var result = c.LLVMGetUndef(opt_ty); + result = c.LLVMBuildInsertValue(self.e.builder, result, val, 0, "ow.val"); + result = c.LLVMBuildInsertValue(self.e.builder, result, c.LLVMConstInt(self.e.cached_i1, 1, 0), 1, "ow.has"); + self.e.mapRef(result); + } else { + // ?Closure → closure struct IS the optional, just pass through + self.e.mapRef(val); + } + } else { + self.e.mapRef(val); + } + } + + pub fn emitOptionalUnwrap(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(val_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // { T, i1 } → extract field 0 + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, 0, "ou.val")); + } else { + // ?Closure → the struct itself is the value + self.e.mapRef(val); + } + } else { + // ?*T → pointer is the value itself + self.e.mapRef(val); + } + } + + pub fn emitOptionalHasValue(self: Ops, un: UnaryOp) void { + const val = self.e.resolveRef(un.operand); + const val_ty = c.LLVMTypeOf(val); + const kind = c.LLVMGetTypeKind(val_ty); + if (kind == c.LLVMStructTypeKind) { + // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) + const num_fields = c.LLVMCountStructElementTypes(val_ty); + const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.e.cached_i1; + if (last_field_ty == self.e.cached_i1) { + // { T, i1 } → extract has_value flag + self.e.mapRef(c.LLVMBuildExtractValue(self.e.builder, val, num_fields - 1, "oh.has")); + } else { + // ?Closure {fn_ptr, env} → check if fn_ptr is null + const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, val, 0, "oh.fn"); + self.e.mapRef(c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oh.nn")); + } + } else { + // ?*T → compare with null + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, val, c.LLVMConstNull(val_ty), "oh.nn"); + self.e.mapRef(is_nonnull); + } + } + + pub fn emitOptionalCoalesce(self: Ops, bin: BinOp) void { + // a ?? b — if a has value, use a's value; otherwise use b + const a = self.e.resolveRef(bin.lhs); + var b_val = self.e.resolveRef(bin.rhs); + const a_ty = c.LLVMTypeOf(a); + const kind = c.LLVMGetTypeKind(a_ty); + if (kind == c.LLVMStructTypeKind) { + const n_fields = c.LLVMCountStructElementTypes(a_ty); + const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null; + const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false; + if (is_ti1) { + // Standard optional {T, i1}: extract has_value and unwrap + const has = c.LLVMBuildExtractValue(self.e.builder, a, 1, "oc.has"); + const unwrapped = c.LLVMBuildExtractValue(self.e.builder, a, 0, "oc.val"); + const uw_ty = c.LLVMTypeOf(unwrapped); + const b_ty = c.LLVMTypeOf(b_val); + if (uw_ty != b_ty) { + b_val = self.e.coerceArg(b_val, uw_ty); + } + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, has, unwrapped, b_val, "oc.sel")); + } else { + // ?Closure {fn_ptr, env}: check if fn_ptr is null + const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, a, 0, "oc.fn"); + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn"); + // Select the full closure struct, not just the fn_ptr + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); + } + } else { + // ?*T — select on null + const is_nonnull = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn"); + self.e.mapRef(c.LLVMBuildSelect(self.e.builder, is_nonnull, a, b_val, "oc.sel")); + } + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 4712652..5a9af27 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -1512,410 +1512,26 @@ pub const LLVMEmitter = struct { }, // ── Struct ops ──────────────────────────────────────────── - .struct_init => |agg| { - const struct_ty = self.toLLVMType(instruction.ty); - const type_kind = c.LLVMGetTypeKind(struct_ty); - // For vector types, use InsertElement instead of InsertValue - const is_vector = type_kind == c.LLVMVectorTypeKind or type_kind == c.LLVMScalableVectorTypeKind; - // For array types, get expected element type for coercion - const is_array = type_kind == c.LLVMArrayTypeKind; - const elem_llvm_ty = if (is_array) c.LLVMGetElementType(struct_ty) else null; - var result = c.LLVMGetUndef(struct_ty); - for (agg.fields, 0..) |field_ref, i| { - var field_val = self.resolveRef(field_ref); - if (is_vector) { - // Coerce element to match vector element type - const vec_elem_ty = c.LLVMGetElementType(struct_ty); - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != vec_elem_ty) { - field_val = self.coerceArg(field_val, vec_elem_ty); - } - const idx = c.LLVMConstInt(self.cached_i32, @intCast(i), 0); - result = c.LLVMBuildInsertElement(self.builder, result, field_val, idx, "vi"); - } else { - // Coerce element to match array element type if needed - if (elem_llvm_ty) |elt| { - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != elt) { - const val_kind = c.LLVMGetTypeKind(val_ty); - const elt_kind = c.LLVMGetTypeKind(elt); - if (val_kind == c.LLVMIntegerTypeKind and elt_kind == c.LLVMIntegerTypeKind) { - const val_w = c.LLVMGetIntTypeWidth(val_ty); - const elt_w = c.LLVMGetIntTypeWidth(elt); - if (val_w > elt_w) { - field_val = c.LLVMBuildTrunc(self.builder, field_val, elt, "atrunc"); - } else if (val_w < elt_w) { - field_val = c.LLVMBuildZExt(self.builder, field_val, elt, "aext"); - } - } - } - } else if (type_kind == c.LLVMStructTypeKind) { - // Coerce struct field value to match declared field type - const n_elts = c.LLVMCountStructElementTypes(struct_ty); - if (n_elts > 0 and i < n_elts) { - const field_ty = c.LLVMStructGetTypeAtIndex(struct_ty, @intCast(i)); - const val_ty = c.LLVMTypeOf(field_val); - if (val_ty != field_ty) { - field_val = self.coerceArg(field_val, field_ty); - } - } - } - result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "si"); - } - } - self.mapRef(result); - }, - .struct_get => |fa| { - const base = self.resolveRef(fa.base); - // Safety: null base means unresolved reference — emit undef - if (base == null) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } else { - // Safety: check that base is an aggregate type (struct/array/vector), not scalar - const base_ty = c.LLVMTypeOf(base); - const base_ty_kind = c.LLVMGetTypeKind(base_ty); - if (base_ty_kind == c.LLVMVectorTypeKind or base_ty_kind == c.LLVMScalableVectorTypeKind) { - // Vector: use ExtractElement with an index - const idx = c.LLVMConstInt(self.cached_i32, @intCast(fa.field_index), 0); - const result = c.LLVMBuildExtractElement(self.builder, base, idx, "ve"); - self.mapRef(result); - } else if (base_ty_kind == c.LLVMStructTypeKind or base_ty_kind == c.LLVMArrayTypeKind) { - // Validate field index is in bounds - const n_fields = if (base_ty_kind == c.LLVMStructTypeKind) c.LLVMCountStructElementTypes(base_ty) else 0; - // Check builder has valid insert point - const insert_bb = c.LLVMGetInsertBlock(self.builder); - if (insert_bb == null or (n_fields == 0 and base_ty_kind == c.LLVMStructTypeKind) or (n_fields > 0 and fa.field_index >= n_fields)) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } else { - const result = c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "sg"); - self.mapRef(result); - } - } else { - // Base is not an aggregate (e.g., placeholder undef of scalar type) - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - } - }, - .struct_gep => |fa| { - const base_ptr = self.resolveRef(fa.base); - // Safety: verify base is a pointer before GEP - const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); - if (base_ty_kind == c.LLVMPointerTypeKind) { - const struct_llvm_ty = if (fa.base_type) |bt| - self.toLLVMType(self.resolveAggregate(bt)) - else - self.resolveGepStructType(fa.base, instruction); - const st_kind = c.LLVMGetTypeKind(struct_llvm_ty); - if (st_kind == c.LLVMStructTypeKind or st_kind == c.LLVMArrayTypeKind) { - const result = c.LLVMBuildStructGEP2(self.builder, struct_llvm_ty, base_ptr, @intCast(fa.field_index), "gep"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, + .struct_init => |agg| self.ops().emitStructInit(instruction, agg), + .struct_get => |fa| self.ops().emitStructGet(instruction, fa), + .struct_gep => |fa| self.ops().emitStructGep(instruction, fa), // ── Enum ops ───────────────────────────────────────────── - .enum_init => |ei| { - if (ei.payload.isNone()) { - // Simple enum (no payload) — just a tag integer - const ty = self.toLLVMType(instruction.ty); - const ty_kind = c.LLVMGetTypeKind(ty); - if (ty_kind == c.LLVMIntegerTypeKind) { - // Plain enum or builtin integer → integer constant - self.mapRef(c.LLVMConstInt(ty, ei.tag, 0)); - } else if (ty_kind == c.LLVMStructTypeKind) { - // Tagged union with no payload — header field 0 holds the tag - const header_ty = c.LLVMStructGetTypeAtIndex(ty, 0); - const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); - var result = c.LLVMGetUndef(ty); - result = c.LLVMBuildInsertValue(self.builder, result, tag_val, 0, "ei.tag"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMConstInt(self.cached_i64, ei.tag, 0)); - } - } else { - // Tagged union with payload — { header, payload_bytes } - const union_ty = self.toLLVMType(instruction.ty); - const header_ty = c.LLVMStructGetTypeAtIndex(union_ty, 0); - const tag_val = c.LLVMConstInt(header_ty, ei.tag, 0); - const payload_val = self.resolveRef(ei.payload); - - // alloca union, store tag, bitcast payload area, store payload - const tmp = c.LLVMBuildAlloca(self.builder, union_ty, "ei.tmp"); - // Store tag at field 0 - const tag_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 0, "ei.tagp"); - _ = c.LLVMBuildStore(self.builder, tag_val, tag_ptr); - // Store payload at field 1 (bitcast the byte array to payload type) - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_ty, tmp, 1, "ei.pp"); - const payload_typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ei.pcast"); - _ = c.LLVMBuildStore(self.builder, payload_val, payload_typed_ptr); - // Load the whole union value - self.mapRef(c.LLVMBuildLoad2(self.builder, union_ty, tmp, "ei.val")); - } - }, - .enum_tag => |un| { - const val = self.resolveRef(un.operand); - // Check if this is a plain enum (integer) or tagged union (struct with tag at 0) - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Tagged union — extract field 0 (tag) - var tag = c.LLVMBuildExtractValue(self.builder, val, 0, "etag"); - // Truncate to declared tag width if needed (e.g. i64 → i32 for u32 tags) - // This is essential for FFI unions where the i64 tag slot contains - // a smaller tag + uninitialized padding (e.g. SDL_Event's u32 type + u32 reserved) - const target_ty = self.toLLVMType(instruction.ty); - const extracted_bits = c.LLVMGetIntTypeWidth(c.LLVMTypeOf(tag)); - const target_bits = c.LLVMGetIntTypeWidth(target_ty); - if (target_bits < extracted_bits) { - tag = c.LLVMBuildTrunc(self.builder, tag, target_ty, "etag.trunc"); - } - self.mapRef(tag); - } else { - // Plain enum — the value IS the tag - self.mapRef(val); - } - }, - .enum_payload => |fa| { - const base = self.resolveRef(fa.base); - const result_ty = self.toLLVMType(instruction.ty); - const base_ty = c.LLVMTypeOf(base); - const base_kind = c.LLVMGetTypeKind(base_ty); - if (base_kind == c.LLVMStructTypeKind) { - // Tagged union: alloca, store, GEP field 1 (payload area), bitcast, load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ep.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ep.pp"); - const typed_ptr = c.LLVMBuildBitCast(self.builder, payload_ptr, self.cached_ptr, "ep.cast"); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, typed_ptr, "ep.val")); - } else { - self.mapRef(c.LLVMGetUndef(result_ty)); - } - }, + .enum_init => |ei| self.ops().emitEnumInit(instruction, ei), + .enum_tag => |un| self.ops().emitEnumTag(instruction, un), + .enum_payload => |fa| self.ops().emitEnumPayload(instruction, fa), // ── Union ops ──────────────────────────────────────────── - .union_get => |fa| { - const base = self.resolveRef(fa.base); - const result_ty = self.toLLVMType(instruction.ty); - // Union field access: reinterpret the union's data area as the target type - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMStructTypeKind) { - // Tagged union { header, payload_bytes } — access payload at field 1 - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, base_ty, tmp, 1, "ug.pp"); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, payload_ptr, "ug.val")); - } else { - // Untagged union [N x i8] — alloca, store, reinterpret-load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ug.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - self.mapRef(c.LLVMBuildLoad2(self.builder, result_ty, tmp, "ug.val")); - } - }, - .union_gep => |fa| { - const base_ptr = self.resolveRef(fa.base); - const base_ty_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(base_ptr)); - if (base_ty_kind == c.LLVMPointerTypeKind) { - const union_llvm_ty = if (fa.base_type) |bt| - self.toLLVMType(self.resolveAggregate(bt)) - else - self.resolveGepStructType(fa.base, instruction); - const st_kind = c.LLVMGetTypeKind(union_llvm_ty); - if (st_kind == c.LLVMStructTypeKind) { - // Tagged union — payload is at field 1 - const payload_ptr = c.LLVMBuildStructGEP2(self.builder, union_llvm_ty, base_ptr, 1, "ugep.pp"); - self.mapRef(payload_ptr); - } else { - // Untagged union — data starts at offset 0 - self.mapRef(base_ptr); - } - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, + .union_get => |fa| self.ops().emitUnionGet(instruction, fa), + .union_gep => |fa| self.ops().emitUnionGep(instruction, fa), // ── Array/Slice ops ─────────────────────────────────────── - .index_get => |bin| { - const base = self.resolveRef(bin.lhs); - const idx = self.resolveRef(bin.rhs); - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMVectorTypeKind or kind == c.LLVMScalableVectorTypeKind) { - // Vector — use extractelement - // Coerce index to i32 if needed - const idx32 = self.coerceArg(idx, self.cached_i32); - self.mapRef(c.LLVMBuildExtractElement(self.builder, base, idx32, "ve")); - } else if (kind == c.LLVMArrayTypeKind) { - // Fixed-size array value — alloca, store, GEP, load - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ig.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx }; - const ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else if (kind == c.LLVMPointerTypeKind) { - // Pointer (many-pointer or raw ptr) — GEP + load - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{idx}; - const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, base, &indices, 1, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract ptr, GEP, load - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ig.data"); - const elem_ty = self.toLLVMType(instruction.ty); - var indices = [_]c.LLVMValueRef{idx}; - const ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &indices, 1, "ig.ptr"); - self.mapRef(c.LLVMBuildLoad2(self.builder, elem_ty, ptr, "ig.val")); - } else { - // Non-aggregate base (lowering error) — emit undef - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - }, - .index_gep => |bin| { - const base = self.resolveRef(bin.lhs); - const idx = self.resolveRef(bin.rhs); - const base_ty = c.LLVMTypeOf(base); - const kind = c.LLVMGetTypeKind(base_ty); - if (kind == c.LLVMArrayTypeKind) { - // Fixed-size array value — alloca, store, GEP - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "igp.tmp"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), idx }; - self.mapRef(c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "igp.ptr")); - } else if (kind == c.LLVMPointerTypeKind) { - // Pointer — GEP with proper element type - const gep_elem = blk: { - // instruction.ty is the result type (ptr to element) - // Resolve the pointee type for the GEP element size - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .pointer => |p| self.toLLVMType(p.pointee), - .many_pointer => |p| self.toLLVMType(p.element), - else => self.cached_i8, // fallback - }; - }; - var indices = [_]c.LLVMValueRef{idx}; - self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, base, &indices, 1, "igp.ptr")); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract ptr, GEP with proper element type - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "igp.data"); - const gep_elem = blk: { - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .pointer => |p| self.toLLVMType(p.pointee), - .many_pointer => |p| self.toLLVMType(p.element), - else => self.cached_i8, - }; - }; - var indices = [_]c.LLVMValueRef{idx}; - self.mapRef(c.LLVMBuildGEP2(self.builder, gep_elem, data, &indices, 1, "igp.ptr")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, - .length => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMArrayTypeKind) { - const len = c.LLVMGetArrayLength2(val_ty); - self.mapRef(c.LLVMConstInt(self.cached_i64, len, 0)); - } else if (kind == c.LLVMStructTypeKind) { - // Slice/string {ptr, len} — extract field 1 (len) - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 1, "len")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_i64)); - } - }, - .data_ptr => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "dptr")); - } else { - self.mapRef(c.LLVMGetUndef(self.cached_ptr)); - } - }, - .subslice => |ss| { - const base = self.resolveRef(ss.base); - var lo = self.resolveRef(ss.lo); - var hi = self.resolveRef(ss.hi); - // Normalize lo/hi to i64 for consistent arithmetic (indices are unsigned) - if (c.LLVMTypeOf(lo) != self.cached_i64) { - lo = c.LLVMBuildZExt(self.builder, lo, self.cached_i64, "ss.lo64"); - } - if (c.LLVMTypeOf(hi) != self.cached_i64) { - hi = c.LLVMBuildZExt(self.builder, hi, self.cached_i64, "ss.hi64"); - } - const base_ty = c.LLVMTypeOf(base); - const base_kind = c.LLVMGetTypeKind(base_ty); - const slice_ty = self.toLLVMType(instruction.ty); - // Resolve element type from the result slice type for correct GEP stride - const elem_ty = blk: { - const info = self.ir_mod.types.get(instruction.ty); - break :blk switch (info) { - .slice => |s| self.toLLVMType(s.element), - else => self.cached_i8, - }; - }; - if (base_kind == c.LLVMStructTypeKind) { - // Slice/string: extract data ptr, GEP by lo - const data = c.LLVMBuildExtractValue(self.builder, base, 0, "ss.data"); - var lo_indices = [_]c.LLVMValueRef{lo}; - const new_ptr = c.LLVMBuildGEP2(self.builder, elem_ty, data, &lo_indices, 1, "ss.ptr"); - var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len"); - // Ensure length is i64 for slice struct {ptr, i64} - if (c.LLVMTypeOf(new_len) != self.cached_i64) { - new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext"); - } - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr"); - result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen"); - self.mapRef(result); - } else if (base_kind == c.LLVMArrayTypeKind) { - // Array: alloca, GEP to element at lo, compute len - const tmp = c.LLVMBuildAlloca(self.builder, base_ty, "ss.arr"); - _ = c.LLVMBuildStore(self.builder, base, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), lo }; - const new_ptr = c.LLVMBuildGEP2(self.builder, base_ty, tmp, &indices, 2, "ss.ptr"); - var new_len = c.LLVMBuildSub(self.builder, hi, lo, "ss.len"); - // Ensure length is i64 for slice struct {ptr, i64} - if (c.LLVMTypeOf(new_len) != self.cached_i64) { - new_len = c.LLVMBuildSExt(self.builder, new_len, self.cached_i64, "ss.ext"); - } - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, new_ptr, 0, "ss.wptr"); - result = c.LLVMBuildInsertValue(self.builder, result, new_len, 1, "ss.wlen"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(slice_ty)); - } - }, - .array_to_slice => |un| { - const arr = self.resolveRef(un.operand); - const arr_ty = c.LLVMTypeOf(arr); - const arr_kind = c.LLVMGetTypeKind(arr_ty); - if (arr_kind == c.LLVMArrayTypeKind) { - const len = c.LLVMGetArrayLength2(arr_ty); - const tmp = c.LLVMBuildAlloca(self.builder, arr_ty, "a2s.tmp"); - _ = c.LLVMBuildStore(self.builder, arr, tmp); - var indices = [_]c.LLVMValueRef{ c.LLVMConstInt(self.cached_i64, 0, 0), c.LLVMConstInt(self.cached_i64, 0, 0) }; - const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, tmp, &indices, 2, "a2s.ptr"); - const slice_ty = self.toLLVMType(instruction.ty); - var result = c.LLVMGetUndef(slice_ty); - result = c.LLVMBuildInsertValue(self.builder, result, elem_ptr, 0, "a2s.wptr"); - const len_val = c.LLVMConstInt(self.cached_i64, len, 0); - result = c.LLVMBuildInsertValue(self.builder, result, len_val, 1, "a2s.wlen"); - self.mapRef(result); - } else { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - } - }, + .index_get => |bin| self.ops().emitIndexGet(instruction, bin), + .index_gep => |bin| self.ops().emitIndexGep(instruction, bin), + .length => |un| self.ops().emitLength(un), + .data_ptr => |un| self.ops().emitDataPtr(un), + .subslice => |ss| self.ops().emitSubslice(instruction, ss), + .array_to_slice => |un| self.ops().emitArrayToSlice(instruction, un), // ── Call extensions ─────────────────────────────────────── .call_builtin => |bi| self.ops().emitCallBuiltin(instruction, bi), @@ -1923,123 +1539,14 @@ pub const LLVMEmitter = struct { .call_closure => |call_op| self.ops().emitCallClosure(instruction, call_op), // ── Tuple ops ──────────────────────────────────────────── - .tuple_init => |agg| { - const tuple_ty = self.toLLVMType(instruction.ty); - var result = c.LLVMGetUndef(tuple_ty); - for (agg.fields, 0..) |field_ref, i| { - const field_val = self.resolveRef(field_ref); - result = c.LLVMBuildInsertValue(self.builder, result, field_val, @intCast(i), "ti"); - } - self.mapRef(result); - }, - .tuple_get => |fa| { - const base = self.resolveRef(fa.base); - self.mapRef(c.LLVMBuildExtractValue(self.builder, base, @intCast(fa.field_index), "tg")); - }, + .tuple_init => |agg| self.ops().emitTupleInit(instruction, agg), + .tuple_get => |fa| self.ops().emitTupleGet(fa), // ── Optional ops ───────────────────────────────────────── - .optional_wrap => |un| { - var val = self.resolveRef(un.operand); - const opt_ty = self.toLLVMType(instruction.ty); - const opt_kind = c.LLVMGetTypeKind(opt_ty); - if (opt_kind == c.LLVMPointerTypeKind) { - // ?*T — pointer is the optional itself (null = none) - self.mapRef(val); - } else if (opt_kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(opt_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(opt_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // ?T → { T, i1 } — wrap value + true flag - const inner_ty = c.LLVMStructGetTypeAtIndex(opt_ty, 0); - val = self.coerceArg(val, inner_ty); - var result = c.LLVMGetUndef(opt_ty); - result = c.LLVMBuildInsertValue(self.builder, result, val, 0, "ow.val"); - result = c.LLVMBuildInsertValue(self.builder, result, c.LLVMConstInt(self.cached_i1, 1, 0), 1, "ow.has"); - self.mapRef(result); - } else { - // ?Closure → closure struct IS the optional, just pass through - self.mapRef(val); - } - } else { - self.mapRef(val); - } - }, - .optional_unwrap => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(val_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // { T, i1 } → extract field 0 - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, 0, "ou.val")); - } else { - // ?Closure → the struct itself is the value - self.mapRef(val); - } - } else { - // ?*T → pointer is the value itself - self.mapRef(val); - } - }, - .optional_has_value => |un| { - const val = self.resolveRef(un.operand); - const val_ty = c.LLVMTypeOf(val); - const kind = c.LLVMGetTypeKind(val_ty); - if (kind == c.LLVMStructTypeKind) { - // Distinguish {T, i1} (real optional) from {ptr, ptr} (?Closure) - const num_fields = c.LLVMCountStructElementTypes(val_ty); - const last_field_ty = if (num_fields > 0) c.LLVMStructGetTypeAtIndex(val_ty, num_fields - 1) else self.cached_i1; - if (last_field_ty == self.cached_i1) { - // { T, i1 } → extract has_value flag - self.mapRef(c.LLVMBuildExtractValue(self.builder, val, num_fields - 1, "oh.has")); - } else { - // ?Closure {fn_ptr, env} → check if fn_ptr is null - const fn_ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "oh.fn"); - self.mapRef(c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oh.nn")); - } - } else { - // ?*T → compare with null - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, val, c.LLVMConstNull(val_ty), "oh.nn"); - self.mapRef(is_nonnull); - } - }, - .optional_coalesce => |bin| { - // a ?? b — if a has value, use a's value; otherwise use b - const a = self.resolveRef(bin.lhs); - var b_val = self.resolveRef(bin.rhs); - const a_ty = c.LLVMTypeOf(a); - const kind = c.LLVMGetTypeKind(a_ty); - if (kind == c.LLVMStructTypeKind) { - const n_fields = c.LLVMCountStructElementTypes(a_ty); - const f1_ty = if (n_fields >= 2) c.LLVMStructGetTypeAtIndex(a_ty, 1) else null; - const is_ti1 = if (f1_ty) |ft| c.LLVMGetTypeKind(ft) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(ft) == 1 else false; - if (is_ti1) { - // Standard optional {T, i1}: extract has_value and unwrap - const has = c.LLVMBuildExtractValue(self.builder, a, 1, "oc.has"); - const unwrapped = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.val"); - const uw_ty = c.LLVMTypeOf(unwrapped); - const b_ty = c.LLVMTypeOf(b_val); - if (uw_ty != b_ty) { - b_val = self.coerceArg(b_val, uw_ty); - } - self.mapRef(c.LLVMBuildSelect(self.builder, has, unwrapped, b_val, "oc.sel")); - } else { - // ?Closure {fn_ptr, env}: check if fn_ptr is null - const fn_ptr = c.LLVMBuildExtractValue(self.builder, a, 0, "oc.fn"); - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, fn_ptr, c.LLVMConstNull(c.LLVMTypeOf(fn_ptr)), "oc.nn"); - // Select the full closure struct, not just the fn_ptr - self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel")); - } - } else { - // ?*T — select on null - const is_nonnull = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, a, c.LLVMConstNull(a_ty), "oc.nn"); - self.mapRef(c.LLVMBuildSelect(self.builder, is_nonnull, a, b_val, "oc.sel")); - } - }, + .optional_wrap => |un| self.ops().emitOptionalWrap(instruction, un), + .optional_unwrap => |un| self.ops().emitOptionalUnwrap(un), + .optional_has_value => |un| self.ops().emitOptionalHasValue(un), + .optional_coalesce => |bin| self.ops().emitOptionalCoalesce(bin), // ── Box/Unbox Any ──────────────────────────────────────── .box_any => |ba| { @@ -2253,7 +1760,7 @@ pub const LLVMEmitter = struct { /// Resolve the struct LLVM type for GEP operations. /// Uses LLVM alloca type when available, falls back to IR type system. - fn resolveGepStructType(self: *LLVMEmitter, base_ref: Ref, instruction: *const Inst) c.LLVMTypeRef { + pub fn resolveGepStructType(self: *LLVMEmitter, base_ref: Ref, instruction: *const Inst) c.LLVMTypeRef { const base_val = self.resolveRef(base_ref); // Strategy 1: base is an alloca — get allocated type directly @@ -2296,7 +1803,7 @@ pub const LLVMEmitter = struct { } /// Resolve through pointer types to get the underlying aggregate type. - fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId { + pub fn resolveAggregate(self: *LLVMEmitter, ty: TypeId) TypeId { if (!ty.isBuiltin()) { const info = self.ir_mod.types.get(ty); if (info == .pointer) return info.pointer.pointee;