// Tests for the IR-to-LLVM emitter (emit_llvm.zig). const std = @import("std"); const types = @import("types.zig"); const inst_mod = @import("inst.zig"); const mod_mod = @import("module.zig"); const emit_mod = @import("emit_llvm.zig"); const TypeId = types.TypeId; const Ref = inst_mod.Ref; const FuncId = inst_mod.FuncId; const Function = inst_mod.Function; const Module = mod_mod.Module; const Builder = mod_mod.Builder; const LLVMEmitter = emit_mod.LLVMEmitter; // ── Helper ────────────────────────────────────────────────────────────── fn str(module: *Module, s: []const u8) types.StringId { return module.types.internString(s); } // ── Tests ─────────────────────────────────────────────────────────────── test "emit: main() returns 42" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func main() -> s64 { return 42; } _ = b.beginFunction(str(&module, "main"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const c42 = b.constInt(42, .s64); b.ret(c42, .s64); b.finalize(); // Emit to LLVM var emitter = LLVMEmitter.init(alloc, &module, "test_ret42", .{}); defer emitter.deinit(); emitter.emit(); // Verify the module is valid try std.testing.expect(emitter.verify()); // Check LLVM IR contains expected patterns const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "define") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64 42") != null); } test "emit: add(a, b) returns a + b" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func add(a: s64, b: s64) -> s64 { return a + b; } const params = &[_]Function.Param{ .{ .name = str(&module, "a"), .ty = .s64 }, .{ .name = str(&module, "b"), .ty = .s64 }, }; _ = b.beginFunction(str(&module, "add"), params, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); // Parameters are refs 0 and 1 — but in our IR they're passed as // arguments to the interpreter. For the LLVM emitter, we need to // load them from LLVM function params. For now, use constInt as // placeholders since we haven't wired up param→ref mapping yet. // // Actually, looking at the IR design: the Builder's inst_counter starts // at 0, and params are accessed differently. The lowering pass emits // alloca+store for params. For this test, we use const_int to test // the add instruction directly. const a = b.constInt(10, .s64); const a_b = b.constInt(32, .s64); const sum = b.add(a, a_b, .s64); b.ret(sum, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_add", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "add") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: float arithmetic" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); _ = b.beginFunction(str(&module, "fmath"), &.{}, .f64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const a = b.constFloat(3.14, .f64); const a_b = b.constFloat(2.0, .f64); const sum = b.add(a, a_b, .f64); const product = b.mul(sum, a_b, .f64); b.ret(product, .f64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_float", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "fadd") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "fmul") != null); } test "emit: negation" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); _ = b.beginFunction(str(&module, "negate"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(7, .s64); const neg = b.emit(.{ .neg = .{ .operand = val } }, .s64); b.ret(neg, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_neg", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // LLVM represents neg as "sub nsw i64 0, %val" or "sub i64 0, %val" try std.testing.expect(std.mem.indexOf(u8, ir_str, "sub") != null); } test "emit: void function" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); _ = b.beginFunction(str(&module, "noop"), &.{}, .void); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); b.retVoid(); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_void", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret void") != null); } test "emit: alloca, store, load" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f() -> s64 { var x: s64 = 10; return x; } _ = b.beginFunction(str(&module, "f"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const x_ptr = b.alloca(.s64); // alloca s64 → *s64 const ten = b.constInt(10, .s64); b.store(x_ptr, ten); // store 10 → *x const loaded = b.load(x_ptr, .s64); // load *x → s64 b.ret(loaded, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_mem", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "alloca") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "store") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "load") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: comparison and branch" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f() -> s64 { if (10 < 20) return 1; else return 0; } _ = b.beginFunction(str(&module, "cmpfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); const then_bb = b.appendBlock(str(&module, "then"), &.{}); const else_bb = b.appendBlock(str(&module, "else"), &.{}); b.switchToBlock(entry); const a = b.constInt(10, .s64); const b_val = b.constInt(20, .s64); const cond = b.cmpLt(a, b_val); b.condBr(cond, then_bb, &.{}, else_bb, &.{}); b.switchToBlock(then_bb); const one = b.constInt(1, .s64); b.ret(one, .s64); b.switchToBlock(else_bb); const zero = b.constInt(0, .s64); b.ret(zero, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_cmp", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "icmp") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "br i1") != null); } test "emit: heap_alloc and heap_free" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f() -> void { p = malloc(64); free(p); } _ = b.beginFunction(str(&module, "heapfn"), &.{}, .void); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const size = b.constInt(64, .s64); const ptr_ty = module.types.ptrTo(.void); const ptr = b.emit(.{ .heap_alloc = .{ .operand = size } }, ptr_ty); b.emit(.{ .heap_free = .{ .operand = ptr } }, .void); b.retVoid(); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_heap", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "malloc") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "free") != null); } test "emit: function call" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func add(a: s64, b: s64) -> s64 { return a + b; } (using constants) const add_id = b.beginFunction(str(&module, "addfn"), &[_]Function.Param{ .{ .name = str(&module, "a"), .ty = .s64 }, .{ .name = str(&module, "b"), .ty = .s64 }, }, .s64); const add_entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(add_entry); const p0 = b.constInt(0, .s64); // placeholder const p1 = b.constInt(0, .s64); const sum = b.add(p0, p1, .s64); b.ret(sum, .s64); b.finalize(); // func main() -> s64 { return addfn(3, 4); } _ = b.beginFunction(str(&module, "main"), &.{}, .s64); const main_entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(main_entry); const three = b.constInt(3, .s64); const four = b.constInt(4, .s64); const result = b.call(add_id, &.{ three, four }, .s64); b.ret(result, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_call", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "call") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "addfn") != null); } test "emit: widen conversion s32 to s64" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); _ = b.beginFunction(str(&module, "wfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(42, .s32); const wide = b.widen(val, .s32, .s64); b.ret(wide, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_widen", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "sext") != null); } test "emit: type conversion toLLVMType" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var emitter = LLVMEmitter.init(alloc, &module, "test_types", .{}); defer emitter.deinit(); // Just verify toLLVMType doesn't crash for all builtin types _ = emitter.toLLVMType(.void); _ = emitter.toLLVMType(.bool); _ = emitter.toLLVMType(.s8); _ = emitter.toLLVMType(.s16); _ = emitter.toLLVMType(.s32); _ = emitter.toLLVMType(.s64); _ = emitter.toLLVMType(.u8); _ = emitter.toLLVMType(.u16); _ = emitter.toLLVMType(.u32); _ = emitter.toLLVMType(.u64); _ = emitter.toLLVMType(.f32); _ = emitter.toLLVMType(.f64); _ = emitter.toLLVMType(.string); _ = emitter.toLLVMType(.any); _ = emitter.toLLVMType(.noreturn); } // ── Struct/Enum/Union tests ───────────────────────────────────────── test "emit: struct_init and struct_get" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); // Create a struct type: Point { x: s64, y: s64 } const fields = &[_]types.TypeInfo.StructInfo.Field{ .{ .name = str(&module, "x"), .ty = .s64 }, .{ .name = str(&module, "y"), .ty = .s64 }, }; const owned_fields = alloc.dupe(types.TypeInfo.StructInfo.Field, fields) catch unreachable; defer alloc.free(owned_fields); const point_ty = module.types.intern(.{ .@"struct" = .{ .name = str(&module, "Point"), .fields = owned_fields, } }); var b = Builder.init(&module); // func f() -> s64 { p = Point{10, 20}; return p.y; } _ = b.beginFunction(str(&module, "f"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const x = b.constInt(10, .s64); const y = b.constInt(20, .s64); const p = b.structInit(&.{ x, y }, point_ty); const py = b.structGet(p, 1, .s64); b.ret(py, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_struct", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: struct_gep (pointer to field)" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); // Create struct type const fields = &[_]types.TypeInfo.StructInfo.Field{ .{ .name = str(&module, "x"), .ty = .s64 }, .{ .name = str(&module, "y"), .ty = .s64 }, }; const owned_fields = alloc.dupe(types.TypeInfo.StructInfo.Field, fields) catch unreachable; defer alloc.free(owned_fields); const point_ty = module.types.intern(.{ .@"struct" = .{ .name = str(&module, "Point"), .fields = owned_fields, } }); const ptr_s64 = module.types.ptrTo(.s64); var b = Builder.init(&module); // func f() -> s64 { var p: Point; p.y = 42; return p.y; } _ = b.beginFunction(str(&module, "gepfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const p_ptr = b.alloca(point_ty); const y_ptr = b.structGepTyped(p_ptr, 1, ptr_s64, point_ty); const c42 = b.constInt(42, .s64); b.store(y_ptr, c42); const loaded = b.load(y_ptr, .s64); b.ret(loaded, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_gep", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "getelementptr") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "store") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: enum_init and enum_tag (plain enum)" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); // Create a plain enum type: Color { Red, Green, Blue } const variants = &[_]types.StringId{ str(&module, "Red"), str(&module, "Green"), str(&module, "Blue"), }; const owned_variants = alloc.dupe(types.StringId, variants) catch unreachable; defer alloc.free(owned_variants); const color_ty = module.types.intern(.{ .@"enum" = .{ .name = str(&module, "Color"), .variants = owned_variants, } }); var b = Builder.init(&module); // func f() -> s64 { c = Color.Green; return tag(c); } _ = b.beginFunction(str(&module, "enumfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const green = b.enumInit(1, Ref.none, color_ty); // Green = tag 1 const tag = b.enumTag(green, .s32); // Widen tag from s32 to s64 for the return const wide = b.widen(tag, .s32, .s64); b.ret(wide, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_enum", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // Plain enum is just an integer constant try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); // Create a tagged union: Shape { Circle: f64, Rect: s64 } const ufields = &[_]types.TypeInfo.StructInfo.Field{ .{ .name = str(&module, "Circle"), .ty = .f64 }, .{ .name = str(&module, "Rect"), .ty = .s64 }, }; const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable; defer alloc.free(owned_ufields); const shape_ty = module.types.intern(.{ .tagged_union = .{ .name = str(&module, "Shape"), .fields = owned_ufields, .tag_type = .s64, } }); var b = Builder.init(&module); // func f() -> f64 { s = Shape.Circle(3.14); tag = enum_tag(s); payload = enum_payload(s, 0); return payload; } _ = b.beginFunction(str(&module, "unionfn"), &.{}, .f64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const radius = b.constFloat(3.14, .f64); const shape = b.enumInit(0, radius, shape_ty); // Circle = tag 0 const tag = b.emit(.{ .enum_tag = .{ .operand = shape } }, .s64); _ = tag; // tag is used but we just check it doesn't crash const payload = b.emit(.{ .enum_payload = .{ .base = shape, .field_index = 0 } }, .f64); b.ret(payload, .f64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_union", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); } test "emit: union_get (reinterpret union field)" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); // Untagged union: Data { as_int: s64, as_float: f64 } const ufields = &[_]types.TypeInfo.StructInfo.Field{ .{ .name = str(&module, "as_int"), .ty = .s64 }, .{ .name = str(&module, "as_float"), .ty = .f64 }, }; const owned_ufields = alloc.dupe(types.TypeInfo.StructInfo.Field, ufields) catch unreachable; defer alloc.free(owned_ufields); const data_ty = module.types.intern(.{ .@"union" = .{ .name = str(&module, "Data"), .fields = owned_ufields, } }); var b = Builder.init(&module); // func f() -> s64 { d = Data.as_int(42); return union_get(d, 0) as s64; } _ = b.beginFunction(str(&module, "ugfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(42, .s64); const d = b.enumInit(0, val, data_ty); const got = b.emit(.{ .union_get = .{ .base = d, .field_index = 0 } }, .s64); b.ret(got, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_union_get", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // Should contain alloca + store + GEP + load pattern try std.testing.expect(std.mem.indexOf(u8, ir_str, "alloca") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "load") != null); } // ── Array/Slice tests ─────────────────────────────────────────────── test "emit: array index_get" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const arr_ty = module.types.arrayOf(.s64, 3); var b = Builder.init(&module); // func f() -> s64 { arr: [3]s64 = ---; return arr[1]; } _ = b.beginFunction(str(&module, "arr_idx"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const undef_arr = b.emit(.{ .const_undef = {} }, arr_ty); const idx = b.constInt(1, .s64); const elem = b.emit(.{ .index_get = .{ .lhs = undef_arr, .rhs = idx } }, .s64); b.ret(elem, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_arr_idx", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "getelementptr") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: length on slice" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f(s: string) -> s64 { return s.len; } _ = b.beginFunction(str(&module, "strlen"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); // Build a string constant {ptr, len} const s = b.constString(str(&module, "hello")); const len = b.emit(.{ .length = .{ .operand = s } }, .s64); b.ret(len, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_len", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: data_ptr on slice" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const ptr_ty = module.types.ptrTo(.u8); var b = Builder.init(&module); // func f() -> *u8 { s = "hello"; return s.ptr; } _ = b.beginFunction(str(&module, "dptr"), &.{}, ptr_ty); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const s = b.constString(str(&module, "test")); const ptr = b.emit(.{ .data_ptr = .{ .operand = s } }, ptr_ty); b.ret(ptr, ptr_ty); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_dptr", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret ptr") != null); } test "emit: array_to_slice" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const arr_ty = module.types.arrayOf(.s64, 4); const slice_ty = module.types.sliceOf(.s64); var b = Builder.init(&module); // func f() -> []s64 { var arr: [4]s64 = ---; return arr[:]; } _ = b.beginFunction(str(&module, "a2s"), &.{}, slice_ty); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const undef_arr = b.emit(.{ .const_undef = {} }, arr_ty); const slice = b.emit(.{ .array_to_slice = .{ .operand = undef_arr } }, slice_ty); b.ret(slice, slice_ty); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_a2s", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // Should have GEP for array decay + insertvalue for slice construction try std.testing.expect(std.mem.indexOf(u8, ir_str, "getelementptr") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); } test "emit: subslice" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const slice_ty = module.types.sliceOf(.u8); var b = Builder.init(&module); // func f() -> []u8 { s = "hello"; return s[1..3]; } _ = b.beginFunction(str(&module, "ssfn"), &.{}, slice_ty); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const s = b.constString(str(&module, "hello")); const lo = b.constInt(1, .s64); const hi = b.constInt(3, .s64); const sub = b.emit(.{ .subslice = .{ .base = s, .lo = lo, .hi = hi } }, slice_ty); b.ret(sub, slice_ty); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_subslice", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // Should have GEP for ptr+lo and sub for hi-lo try std.testing.expect(std.mem.indexOf(u8, ir_str, "getelementptr") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "sub") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); } // ── Optional tests ────────────────────────────────────────────────── test "emit: optional_wrap and optional_unwrap (value type)" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const opt_ty = module.types.optionalOf(.s64); var b = Builder.init(&module); // func f() -> s64 { opt = wrap(42); return unwrap(opt); } _ = b.beginFunction(str(&module, "optfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(42, .s64); const wrapped = b.optionalWrap(val, opt_ty); const unwrapped = b.optionalUnwrap(wrapped, .s64); b.ret(unwrapped, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_opt", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); // wrap = insertvalue, unwrap = extractvalue try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i64") != null); } test "emit: optional_has_value" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const opt_ty = module.types.optionalOf(.s64); var b = Builder.init(&module); _ = b.beginFunction(str(&module, "hasfn"), &.{}, .bool); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(10, .s64); const wrapped = b.optionalWrap(val, opt_ty); const has = b.optionalHasValue(wrapped); b.ret(has, .bool); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_has", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i1") != null); } // ── Switch branch test ────────────────────────────────────────────── test "emit: switch_br" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f(x: s64) -> s64 { match x { 0 => 10, 1 => 20, _ => 30 } } _ = b.beginFunction(str(&module, "swfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); const case0 = b.appendBlock(str(&module, "case0"), &.{}); const case1 = b.appendBlock(str(&module, "case1"), &.{}); const default_bb = b.appendBlock(str(&module, "default"), &.{}); b.switchToBlock(entry); const x = b.constInt(1, .s64); const cases = alloc.dupe(inst_mod.SwitchBranch.Case, &.{ .{ .value = 0, .target = case0, .args = &.{} }, .{ .value = 1, .target = case1, .args = &.{} }, }) catch unreachable; defer alloc.free(cases); b.emitVoid(.{ .switch_br = .{ .operand = x, .cases = cases, .default = default_bb, .default_args = &.{}, } }, .void); b.switchToBlock(case0); b.ret(b.constInt(10, .s64), .s64); b.switchToBlock(case1); b.ret(b.constInt(20, .s64), .s64); b.switchToBlock(default_bb); b.ret(b.constInt(30, .s64), .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_switch", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "switch") != null); } // ── Closure test ──────────────────────────────────────────────────── test "emit: closure_create" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); const closure_ty = module.types.closureType(&.{.s64}, .s64); var b = Builder.init(&module); // Create a dummy trampoline function const tramp_id = b.beginFunction(str(&module, "tramp"), &[_]inst_mod.Function.Param{ .{ .name = str(&module, "env"), .ty = .s64 }, .{ .name = str(&module, "x"), .ty = .s64 }, }, .s64); const tramp_entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(tramp_entry); b.ret(b.constInt(0, .s64), .s64); b.finalize(); // func f() -> closure { return closure_create(tramp, null); } _ = b.beginFunction(str(&module, "mkclose"), &.{}, closure_ty); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const cl = b.emit(.{ .closure_create = .{ .func = tramp_id, .env = Ref.none } }, closure_ty); b.ret(cl, closure_ty); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_closure", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); } // ── Box/Unbox Any test ────────────────────────────────────────────── test "emit: box_any and unbox_any" { const alloc = std.testing.allocator; var module = Module.init(alloc); defer module.deinit(); var b = Builder.init(&module); // func f() -> s64 { a = box(42); return unbox(a); } _ = b.beginFunction(str(&module, "anyfn"), &.{}, .s64); const entry = b.appendBlock(str(&module, "entry"), &.{}); b.switchToBlock(entry); const val = b.constInt(42, .s64); const boxed = b.emit(.{ .box_any = .{ .operand = val, .source_type = .s64 } }, .any); const unboxed = b.emit(.{ .unbox_any = .{ .operand = boxed } }, .s64); b.ret(unboxed, .s64); b.finalize(); var emitter = LLVMEmitter.init(alloc, &module, "test_any", .{}); defer emitter.deinit(); emitter.emit(); try std.testing.expect(emitter.verify()); const ir_str = emitter.dumpToString(); try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null); try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null); }