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<Op>(...); 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).
This commit is contained in:
agra
2026-06-03 12:03:45 +03:00
parent e58d2a1eed
commit 1be16511ec
2 changed files with 574 additions and 522 deletions

View File

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

View File

@@ -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;