650 lines
24 KiB
Zig
650 lines
24 KiB
Zig
// Tests for the IR interpreter (interp.zig).
|
|
// Includes basic interpreter tests and comptime parity tests.
|
|
|
|
const std = @import("std");
|
|
const types = @import("types.zig");
|
|
const inst_mod = @import("inst.zig");
|
|
const mod_mod = @import("module.zig");
|
|
const interp_mod = @import("interp.zig");
|
|
|
|
const TypeId = types.TypeId;
|
|
const Ref = inst_mod.Ref;
|
|
const BlockId = inst_mod.BlockId;
|
|
const FuncId = inst_mod.FuncId;
|
|
const Function = inst_mod.Function;
|
|
const Module = mod_mod.Module;
|
|
const Builder = mod_mod.Builder;
|
|
const Interpreter = interp_mod.Interpreter;
|
|
const Value = interp_mod.Value;
|
|
|
|
// ── Helper ──────────────────────────────────────────────────────────────
|
|
|
|
fn str(module: *Module, s: []const u8) types.StringId {
|
|
return module.types.internString(s);
|
|
}
|
|
|
|
// ── Basic interpreter tests (migrated from interp.zig) ──────────────────
|
|
|
|
test "interpret: compute(5) = 25" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
// func compute(x: s64) -> s64 { return x * x; }
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "compute"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "compute"), params, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const x_ref = Ref.fromIndex(0);
|
|
const result = b.mul(x_ref, x_ref, .s64);
|
|
b.ret(result, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 5 }});
|
|
try std.testing.expectEqual(@as(i64, 25), val.asInt().?);
|
|
}
|
|
|
|
test "interpret: if/else branching" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "abs"), params, .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 x = Ref.fromIndex(0);
|
|
const zero = b.constInt(0, .s64);
|
|
const is_neg = b.cmpLt(x, zero);
|
|
b.condBr(is_neg, then_bb, &.{}, else_bb, &.{});
|
|
|
|
b.switchToBlock(then_bb);
|
|
const neg_x = b.emit(.{ .neg = .{ .operand = x } }, .s64);
|
|
b.ret(neg_x, .s64);
|
|
|
|
b.switchToBlock(else_bb);
|
|
b.ret(x, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
const val1 = try interp.call(FuncId.fromIndex(0), &.{.{ .int = -7 }});
|
|
try std.testing.expectEqual(@as(i64, 7), val1.asInt().?);
|
|
|
|
const val2 = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 3 }});
|
|
try std.testing.expectEqual(@as(i64, 3), val2.asInt().?);
|
|
}
|
|
|
|
test "interpret: function calling another function" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
// func square(x: s64) -> s64 { return x * x; }
|
|
const params_sq = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "square"), params_sq, .s64);
|
|
const entry1 = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry1);
|
|
const x = Ref.fromIndex(0);
|
|
const sq = b.mul(x, x, .s64);
|
|
b.ret(sq, .s64);
|
|
b.finalize();
|
|
|
|
// func sum_of_squares(a, b) -> s64 { return square(a) + square(b); }
|
|
const params_ss = &[_]Function.Param{
|
|
.{ .name = str(&module, "a"), .ty = .s64 },
|
|
.{ .name = str(&module, "b"), .ty = .s64 },
|
|
};
|
|
_ = b.beginFunction(str(&module, "sum_of_squares"), params_ss, .s64);
|
|
const entry2 = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry2);
|
|
const a = Ref.fromIndex(0);
|
|
const b_param = Ref.fromIndex(1);
|
|
const sq_a = b.call(FuncId.fromIndex(0), &.{a}, .s64);
|
|
const sq_b = b.call(FuncId.fromIndex(0), &.{b_param}, .s64);
|
|
const sum = b.add(sq_a, sq_b, .s64);
|
|
b.ret(sum, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
const val = try interp.call(FuncId.fromIndex(1), &.{ .{ .int = 3 }, .{ .int = 4 } });
|
|
try std.testing.expectEqual(@as(i64, 25), val.asInt().?);
|
|
}
|
|
|
|
test "interpret: alloca/store/load" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const slot = b.alloca(.s64);
|
|
const ten = b.constInt(10, .s64);
|
|
b.store(slot, ten);
|
|
const loaded = b.load(slot, .s64);
|
|
const five = b.constInt(5, .s64);
|
|
const sum = b.add(loaded, five, .s64);
|
|
b.store(slot, sum);
|
|
const result = b.load(slot, .s64);
|
|
b.ret(result, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 15), val.asInt().?);
|
|
}
|
|
|
|
// ── Comptime parity tests ───────────────────────────────────────────────
|
|
|
|
// ── Test: while loop (sumOf10 from 15-while.sx) ─────────────────────────
|
|
// sumOf10 :: () -> s32 { i:=1; s:=0; while i<=10 { s+=i; i+=1; } s; }
|
|
// Expected: 55
|
|
|
|
test "comptime: while loop — sumOf10 = 55" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "sumOf10"), &.{}, .s64);
|
|
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
const hdr = b.appendBlock(str(&module, "while.hdr"), &.{});
|
|
const body = b.appendBlock(str(&module, "while.body"), &.{});
|
|
const exit = b.appendBlock(str(&module, "while.exit"), &.{});
|
|
|
|
// entry: i=1, s=0, br while.hdr
|
|
b.switchToBlock(entry);
|
|
const i_slot = b.alloca(.s64);
|
|
const one = b.constInt(1, .s64);
|
|
b.store(i_slot, one);
|
|
const s_slot = b.alloca(.s64);
|
|
const zero = b.constInt(0, .s64);
|
|
b.store(s_slot, zero);
|
|
b.br(hdr, &.{});
|
|
|
|
// while.hdr: if i <= 10 → body, else → exit
|
|
b.switchToBlock(hdr);
|
|
const i_load = b.load(i_slot, .s64);
|
|
const ten = b.constInt(10, .s64);
|
|
const cond = b.emit(.{ .cmp_le = .{ .lhs = i_load, .rhs = ten } }, .bool);
|
|
b.condBr(cond, body, &.{}, exit, &.{});
|
|
|
|
// while.body: s += i; i += 1; br while.hdr
|
|
b.switchToBlock(body);
|
|
const s_load = b.load(s_slot, .s64);
|
|
const i_load2 = b.load(i_slot, .s64);
|
|
const s_new = b.add(s_load, i_load2, .s64);
|
|
b.store(s_slot, s_new);
|
|
const i_load3 = b.load(i_slot, .s64);
|
|
const one2 = b.constInt(1, .s64);
|
|
const i_new = b.add(i_load3, one2, .s64);
|
|
b.store(i_slot, i_new);
|
|
b.br(hdr, &.{});
|
|
|
|
// while.exit: return s
|
|
b.switchToBlock(exit);
|
|
const s_final = b.load(s_slot, .s64);
|
|
b.ret(s_final, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 55), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: optional coalesce (ct_sum from 32-optionals.sx) ────────────────
|
|
// ct_sum :: () -> s32 { x:?s32=42; y:?s32=null; return (x??0)+(y??99); }
|
|
// Expected: 42 + 99 = 141
|
|
|
|
test "comptime: optional coalesce — ct_sum = 141" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "ct_sum"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// x: ?s32 = 42 → alloca, store 42
|
|
const x_slot = b.alloca(.s64);
|
|
const forty_two = b.constInt(42, .s64);
|
|
b.store(x_slot, forty_two);
|
|
|
|
// y: ?s32 = null → alloca, store null
|
|
const y_slot = b.alloca(.s64);
|
|
const null_val = b.constNull(.s64);
|
|
b.store(y_slot, null_val);
|
|
|
|
// (x ?? 0)
|
|
const x_load = b.load(x_slot, .s64);
|
|
const zero = b.constInt(0, .s64);
|
|
const x_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = x_load, .rhs = zero } }, .s64);
|
|
|
|
// (y ?? 99)
|
|
const y_load = b.load(y_slot, .s64);
|
|
const ninety_nine = b.constInt(99, .s64);
|
|
const y_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = y_load, .rhs = ninety_nine } }, .s64);
|
|
|
|
// return x_coalesced + y_coalesced
|
|
const sum = b.add(x_coalesced, y_coalesced, .s64);
|
|
b.ret(sum, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 141), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: optional unwrap (ct_opt_unwrap from 50-smoke.sx) ───────────────
|
|
// ct_opt_unwrap :: () -> s32 { x:?s32 = 77; return x!; }
|
|
// Expected: 77
|
|
|
|
test "comptime: optional unwrap — 77" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "ct_opt_unwrap"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const slot = b.alloca(.s64);
|
|
const val77 = b.constInt(77, .s64);
|
|
b.store(slot, val77);
|
|
|
|
const loaded = b.load(slot, .s64);
|
|
const unwrapped = b.optionalUnwrap(loaded, .s64);
|
|
b.ret(unwrapped, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 77), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: recursive fibonacci ────────────────────────────────────────────
|
|
// fib :: (n: s64) -> s64 { if n <= 1 return n; return fib(n-1) + fib(n-2); }
|
|
// Expected: fib(10) = 55
|
|
|
|
test "comptime: recursive fibonacci — fib(10) = 55" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "n"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "fib"), params, .s64);
|
|
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
const base_bb = b.appendBlock(str(&module, "base"), &.{});
|
|
const rec_bb = b.appendBlock(str(&module, "recurse"), &.{});
|
|
|
|
// entry: if n <= 1 → base, else → recurse
|
|
b.switchToBlock(entry);
|
|
const n = Ref.fromIndex(0);
|
|
const one = b.constInt(1, .s64);
|
|
const is_base = b.emit(.{ .cmp_le = .{ .lhs = n, .rhs = one } }, .bool);
|
|
b.condBr(is_base, base_bb, &.{}, rec_bb, &.{});
|
|
|
|
// base: return n
|
|
b.switchToBlock(base_bb);
|
|
b.ret(n, .s64);
|
|
|
|
// recurse: return fib(n-1) + fib(n-2)
|
|
b.switchToBlock(rec_bb);
|
|
const n_minus_1 = b.sub(n, one, .s64);
|
|
const two = b.constInt(2, .s64);
|
|
const n_minus_2 = b.sub(n, two, .s64);
|
|
const fib1 = b.call(FuncId.fromIndex(0), &.{n_minus_1}, .s64);
|
|
const fib2 = b.call(FuncId.fromIndex(0), &.{n_minus_2}, .s64);
|
|
const sum = b.add(fib1, fib2, .s64);
|
|
b.ret(sum, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 10 }});
|
|
try std.testing.expectEqual(@as(i64, 55), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: compute(5) = 7 (from 05-run.sx) ──────────────────────────────
|
|
// compute :: (v: s32) -> s32 => v + 2;
|
|
// Expected: compute(5) = 7
|
|
|
|
test "comptime: compute(5) = 7" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "v"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "compute"), params, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const v = Ref.fromIndex(0);
|
|
const two = b.constInt(2, .s64);
|
|
const result = b.add(v, two, .s64);
|
|
b.ret(result, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 5 }});
|
|
try std.testing.expectEqual(@as(i64, 7), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: chained comptime (CT_CHAIN from 50-smoke.sx) ───────────────────
|
|
// add :: (a: s32, b: s32) -> s32 => a + b;
|
|
// CT_VAL :: #run add(10, 15); → 25
|
|
// CT_CHAIN :: #run add(CT_VAL, 5); → 30
|
|
// Simulates calling add(25, 5) to verify chaining works.
|
|
|
|
test "comptime: chained — add(add(10,15), 5) = 30" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// func add(a, b) -> 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);
|
|
const a = Ref.fromIndex(0);
|
|
const b_ref = Ref.fromIndex(1);
|
|
const sum = b.add(a, b_ref, .s64);
|
|
b.ret(sum, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
// First: add(10, 15) = 25
|
|
const ct_val = try interp.call(FuncId.fromIndex(0), &.{ .{ .int = 10 }, .{ .int = 15 } });
|
|
try std.testing.expectEqual(@as(i64, 25), ct_val.asInt().?);
|
|
|
|
// Then: add(25, 5) = 30 (chained)
|
|
const ct_chain = try interp.call(FuncId.fromIndex(0), &.{ ct_val, .{ .int = 5 } });
|
|
try std.testing.expectEqual(@as(i64, 30), ct_chain.asInt().?);
|
|
}
|
|
|
|
// ── Test: struct init + field access ─────────────────────────────────────
|
|
// p := Point{x: 3, y: 4}; return p.x + p.y;
|
|
// Expected: 7
|
|
|
|
test "comptime: struct init and field access — 7" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_struct"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// Point{x: 3, y: 4}
|
|
const three = b.constInt(3, .s64);
|
|
const four = b.constInt(4, .s64);
|
|
const point = b.structInit(&.{ three, four }, .s64);
|
|
|
|
// p.x + p.y
|
|
const px = b.structGet(point, 0, .s64);
|
|
const py = b.structGet(point, 1, .s64);
|
|
const sum = b.add(px, py, .s64);
|
|
b.ret(sum, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 7), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: float arithmetic ──────────────────────────────────────────────
|
|
// compute :: (x: f64) -> f64 { return x * 2.5 + 1.0; }
|
|
// Expected: compute(3.0) = 8.5
|
|
|
|
test "comptime: float arithmetic — 8.5" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .f64 }};
|
|
_ = b.beginFunction(str(&module, "compute_f"), params, .f64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const x = Ref.fromIndex(0);
|
|
const two_five = b.constFloat(2.5, .f64);
|
|
const product = b.mul(x, two_five, .f64);
|
|
const one = b.constFloat(1.0, .f64);
|
|
const result = b.add(product, one, .f64);
|
|
b.ret(result, .f64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{.{ .float = 3.0 }});
|
|
try std.testing.expectEqual(@as(f64, 8.5), val.asFloat().?);
|
|
}
|
|
|
|
// ── Test: boolean logic ─────────────────────────────────────────────────
|
|
// test :: (a: bool, b: bool) -> bool { return (a and b) or (not a); }
|
|
// Expected: test(true, false) = true (because not a = false, a and b = false, false or false... wait)
|
|
// Actually: a=true, b=false → (true and false) or (not true) = false or false = false
|
|
// test(false, true) → (false and true) or (not false) = false or true = true
|
|
|
|
test "comptime: boolean logic" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{
|
|
.{ .name = str(&module, "a"), .ty = .bool },
|
|
.{ .name = str(&module, "b"), .ty = .bool },
|
|
};
|
|
_ = b.beginFunction(str(&module, "bool_test"), params, .bool);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const a_ref = Ref.fromIndex(0);
|
|
const b_ref = Ref.fromIndex(1);
|
|
const and_ab = b.emit(.{ .bool_and = .{ .lhs = a_ref, .rhs = b_ref } }, .bool);
|
|
const not_a = b.emit(.{ .bool_not = .{ .operand = a_ref } }, .bool);
|
|
const result = b.emit(.{ .bool_or = .{ .lhs = and_ab, .rhs = not_a } }, .bool);
|
|
b.ret(result, .bool);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
// test(true, false) = false or false = false
|
|
const val1 = try interp.call(FuncId.fromIndex(0), &.{ .{ .boolean = true }, .{ .boolean = false } });
|
|
try std.testing.expectEqual(false, val1.asBool().?);
|
|
|
|
// test(false, true) = false or true = true
|
|
const val2 = try interp.call(FuncId.fromIndex(0), &.{ .{ .boolean = false }, .{ .boolean = true } });
|
|
try std.testing.expectEqual(true, val2.asBool().?);
|
|
}
|
|
|
|
// ── Test: negation ──────────────────────────────────────────────────────
|
|
|
|
test "comptime: negation — int and float" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// func neg_int(x: s64) -> s64 { return -x; }
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "neg_int"), params, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const x = Ref.fromIndex(0);
|
|
const neg = b.emit(.{ .neg = .{ .operand = x } }, .s64);
|
|
b.ret(neg, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 42 }});
|
|
try std.testing.expectEqual(@as(i64, -42), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: modulo ────────────────────────────────────────────────────────
|
|
|
|
test "comptime: modulo — 17 mod 5 = 2" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_mod"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const seventeen = b.constInt(17, .s64);
|
|
const five = b.constInt(5, .s64);
|
|
const result = b.emit(.{ .mod = .{ .lhs = seventeen, .rhs = five } }, .s64);
|
|
b.ret(result, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 2), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: switch_br (enum tag dispatch) ──────────────────────────────────
|
|
// Simulates: match tag { 0 => 10, 1 => 20, else => 30 }
|
|
|
|
test "comptime: switch_br dispatch" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "tag"), .ty = .s64 }};
|
|
_ = b.beginFunction(str(&module, "dispatch"), params, .s64);
|
|
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
const case0 = b.appendBlock(str(&module, "case0"), &.{});
|
|
const case1 = b.appendBlock(str(&module, "case1"), &.{});
|
|
const default = b.appendBlock(str(&module, "default"), &.{});
|
|
|
|
b.switchToBlock(entry);
|
|
const tag = Ref.fromIndex(0);
|
|
b.switchBr(tag, &.{
|
|
.{ .value = 0, .target = case0, .args = &.{} },
|
|
.{ .value = 1, .target = case1, .args = &.{} },
|
|
}, default, &.{});
|
|
|
|
b.switchToBlock(case0);
|
|
const ten = b.constInt(10, .s64);
|
|
b.ret(ten, .s64);
|
|
|
|
b.switchToBlock(case1);
|
|
const twenty = b.constInt(20, .s64);
|
|
b.ret(twenty, .s64);
|
|
|
|
b.switchToBlock(default);
|
|
const thirty = b.constInt(30, .s64);
|
|
b.ret(thirty, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
|
|
const v0 = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 0 }});
|
|
try std.testing.expectEqual(@as(i64, 10), v0.asInt().?);
|
|
|
|
const v1 = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 1 }});
|
|
try std.testing.expectEqual(@as(i64, 20), v1.asInt().?);
|
|
|
|
const v2 = try interp.call(FuncId.fromIndex(0), &.{.{ .int = 99 }});
|
|
try std.testing.expectEqual(@as(i64, 30), v2.asInt().?);
|
|
}
|
|
|
|
// ── Test: enum init + tag extraction ────────────────────────────────────
|
|
|
|
test "comptime: enum init and tag" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_enum"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// Create enum with tag=2, no payload
|
|
const e = b.enumInit(2, Ref.none, .s64);
|
|
const tag = b.emit(.{ .enum_tag = .{ .operand = e } }, .s64);
|
|
b.ret(tag, .s64);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const val = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 2), val.asInt().?);
|
|
}
|
|
|
|
// ── Test: conversion (widen/narrow passthrough) ─────────────────────────
|
|
|
|
test "comptime: widen/narrow passthrough" {
|
|
const alloc = std.testing.allocator;
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_conv"), &.{}, .s64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const val = b.constInt(42, .s32);
|
|
const widened = b.emit(.{ .widen = .{ .operand = val, .from = .s32, .to = .s64 } }, .s64);
|
|
const narrowed = b.emit(.{ .narrow = .{ .operand = widened, .from = .s64, .to = .s32 } }, .s32);
|
|
b.ret(narrowed, .s32);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const result = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(@as(i64, 42), result.asInt().?);
|
|
}
|