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