Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.
Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).
Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.
zig build test: 426/426; examples suite: 595/595.
842 lines
32 KiB
Zig
842 lines
32 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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
// func compute(x: i64) -> i64 { return x * x; }
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "compute"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "compute"), params, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const x_ref = Ref.fromIndex(0);
|
|
const result = b.mul(x_ref, x_ref, .i64);
|
|
b.ret(result, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "abs"), params, .i64);
|
|
|
|
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, .i64);
|
|
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 } }, .i64);
|
|
b.ret(neg_x, .i64);
|
|
|
|
b.switchToBlock(else_bb);
|
|
b.ret(x, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
// func square(x: i64) -> i64 { return x * x; }
|
|
const params_sq = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "square"), params_sq, .i64);
|
|
const entry1 = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry1);
|
|
const x = Ref.fromIndex(0);
|
|
const sq = b.mul(x, x, .i64);
|
|
b.ret(sq, .i64);
|
|
b.finalize();
|
|
|
|
// func sum_of_squares(a, b) -> i64 { return square(a) + square(b); }
|
|
const params_ss = &[_]Function.Param{
|
|
.{ .name = str(&module, "a"), .ty = .i64 },
|
|
.{ .name = str(&module, "b"), .ty = .i64 },
|
|
};
|
|
_ = b.beginFunction(str(&module, "sum_of_squares"), params_ss, .i64);
|
|
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}, .i64);
|
|
const sq_b = b.call(FuncId.fromIndex(0), &.{b_param}, .i64);
|
|
const sum = b.add(sq_a, sq_b, .i64);
|
|
b.ret(sum, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const slot = b.alloca(.i64);
|
|
const ten = b.constInt(10, .i64);
|
|
b.store(slot, ten);
|
|
const loaded = b.load(slot, .i64);
|
|
const five = b.constInt(5, .i64);
|
|
const sum = b.add(loaded, five, .i64);
|
|
b.store(slot, sum);
|
|
const result = b.load(slot, .i64);
|
|
b.ret(result, .i64);
|
|
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 :: () -> i32 { i:=1; s:=0; while i<=10 { s+=i; i+=1; } s; }
|
|
// Expected: 55
|
|
|
|
test "comptime: while loop — sumOf10 = 55" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "sumOf10"), &.{}, .i64);
|
|
|
|
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(.i64);
|
|
const one = b.constInt(1, .i64);
|
|
b.store(i_slot, one);
|
|
const s_slot = b.alloca(.i64);
|
|
const zero = b.constInt(0, .i64);
|
|
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, .i64);
|
|
const ten = b.constInt(10, .i64);
|
|
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, .i64);
|
|
const i_load2 = b.load(i_slot, .i64);
|
|
const s_new = b.add(s_load, i_load2, .i64);
|
|
b.store(s_slot, s_new);
|
|
const i_load3 = b.load(i_slot, .i64);
|
|
const one2 = b.constInt(1, .i64);
|
|
const i_new = b.add(i_load3, one2, .i64);
|
|
b.store(i_slot, i_new);
|
|
b.br(hdr, &.{});
|
|
|
|
// while.exit: return s
|
|
b.switchToBlock(exit);
|
|
const s_final = b.load(s_slot, .i64);
|
|
b.ret(s_final, .i64);
|
|
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 :: () -> i32 { x:?i32=42; y:?i32=null; return (x??0)+(y??99); }
|
|
// Expected: 42 + 99 = 141
|
|
|
|
test "comptime: optional coalesce — ct_sum = 141" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "ct_sum"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// x: ?i32 = 42 → alloca, store 42
|
|
const x_slot = b.alloca(.i64);
|
|
const forty_two = b.constInt(42, .i64);
|
|
b.store(x_slot, forty_two);
|
|
|
|
// y: ?i32 = null → alloca, store null
|
|
const y_slot = b.alloca(.i64);
|
|
const null_val = b.constNull(.i64);
|
|
b.store(y_slot, null_val);
|
|
|
|
// (x ?? 0)
|
|
const x_load = b.load(x_slot, .i64);
|
|
const zero = b.constInt(0, .i64);
|
|
const x_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = x_load, .rhs = zero } }, .i64);
|
|
|
|
// (y ?? 99)
|
|
const y_load = b.load(y_slot, .i64);
|
|
const ninety_nine = b.constInt(99, .i64);
|
|
const y_coalesced = b.emit(.{ .optional_coalesce = .{ .lhs = y_load, .rhs = ninety_nine } }, .i64);
|
|
|
|
// return x_coalesced + y_coalesced
|
|
const sum = b.add(x_coalesced, y_coalesced, .i64);
|
|
b.ret(sum, .i64);
|
|
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 :: () -> i32 { x:?i32 = 77; return x!; }
|
|
// Expected: 77
|
|
|
|
test "comptime: optional unwrap — 77" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "ct_opt_unwrap"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const slot = b.alloca(.i64);
|
|
const val77 = b.constInt(77, .i64);
|
|
b.store(slot, val77);
|
|
|
|
const loaded = b.load(slot, .i64);
|
|
const unwrapped = b.optionalUnwrap(loaded, .i64);
|
|
b.ret(unwrapped, .i64);
|
|
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: i64) -> i64 { if n <= 1 return n; return fib(n-1) + fib(n-2); }
|
|
// Expected: fib(10) = 55
|
|
|
|
test "comptime: recursive fibonacci — fib(10) = 55" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "n"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "fib"), params, .i64);
|
|
|
|
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, .i64);
|
|
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, .i64);
|
|
|
|
// recurse: return fib(n-1) + fib(n-2)
|
|
b.switchToBlock(rec_bb);
|
|
const n_minus_1 = b.sub(n, one, .i64);
|
|
const two = b.constInt(2, .i64);
|
|
const n_minus_2 = b.sub(n, two, .i64);
|
|
const fib1 = b.call(FuncId.fromIndex(0), &.{n_minus_1}, .i64);
|
|
const fib2 = b.call(FuncId.fromIndex(0), &.{n_minus_2}, .i64);
|
|
const sum = b.add(fib1, fib2, .i64);
|
|
b.ret(sum, .i64);
|
|
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: i32) -> i32 => v + 2;
|
|
// Expected: compute(5) = 7
|
|
|
|
test "comptime: compute(5) = 7" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "v"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "compute"), params, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const v = Ref.fromIndex(0);
|
|
const two = b.constInt(2, .i64);
|
|
const result = b.add(v, two, .i64);
|
|
b.ret(result, .i64);
|
|
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: i32, b: i32) -> i32 => 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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// func add(a, b) -> i64 { return a + b; }
|
|
const params = &[_]Function.Param{
|
|
.{ .name = str(&module, "a"), .ty = .i64 },
|
|
.{ .name = str(&module, "b"), .ty = .i64 },
|
|
};
|
|
_ = b.beginFunction(str(&module, "add"), params, .i64);
|
|
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, .i64);
|
|
b.ret(sum, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_struct"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// Point{x: 3, y: 4}
|
|
const three = b.constInt(3, .i64);
|
|
const four = b.constInt(4, .i64);
|
|
const point = b.structInit(&.{ three, four }, .i64);
|
|
|
|
// p.x + p.y
|
|
const px = b.structGet(point, 0, .i64);
|
|
const py = b.structGet(point, 1, .i64);
|
|
const sum = b.add(px, py, .i64);
|
|
b.ret(sum, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// func neg_int(x: i64) -> i64 { return -x; }
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "x"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "neg_int"), params, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const x = Ref.fromIndex(0);
|
|
const neg = b.emit(.{ .neg = .{ .operand = x } }, .i64);
|
|
b.ret(neg, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_mod"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const seventeen = b.constInt(17, .i64);
|
|
const five = b.constInt(5, .i64);
|
|
const result = b.emit(.{ .mod = .{ .lhs = seventeen, .rhs = five } }, .i64);
|
|
b.ret(result, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
const params = &[_]Function.Param{.{ .name = str(&module, "tag"), .ty = .i64 }};
|
|
_ = b.beginFunction(str(&module, "dispatch"), params, .i64);
|
|
|
|
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, .i64);
|
|
b.ret(ten, .i64);
|
|
|
|
b.switchToBlock(case1);
|
|
const twenty = b.constInt(20, .i64);
|
|
b.ret(twenty, .i64);
|
|
|
|
b.switchToBlock(default);
|
|
const thirty = b.constInt(30, .i64);
|
|
b.ret(thirty, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_enum"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
// Create enum with tag=2, no payload
|
|
const e = b.enumInit(2, Ref.none, .i64);
|
|
const tag = b.emit(.{ .enum_tag = .{ .operand = e } }, .i64);
|
|
b.ret(tag, .i64);
|
|
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" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_conv"), &.{}, .i64);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
|
|
const val = b.constInt(42, .i32);
|
|
const widened = b.emit(.{ .widen = .{ .operand = val, .from = .i32, .to = .i64 } }, .i64);
|
|
const narrowed = b.emit(.{ .narrow = .{ .operand = widened, .from = .i64, .to = .i32 } }, .i32);
|
|
b.ret(narrowed, .i32);
|
|
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().?);
|
|
}
|
|
|
|
// ── Test: const_type produces a Value.type_tag ──────────────────────────
|
|
|
|
test "comptime: const_type yields type_tag" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// Build a fn that returns `i64` as a Type-typed Any value (matches
|
|
// the .any IR type assigned by `constType`). The interp returns the
|
|
// raw Value; we assert on the variant.
|
|
_ = b.beginFunction(str(&module, "test_type_tag"), &.{}, .any);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const t = b.constType(.i64);
|
|
b.ret(t, .any);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const result = try interp.call(FuncId.fromIndex(0), &.{});
|
|
|
|
// The Value MUST be a .type_tag, not an .int — proves the variant
|
|
// is honestly distinguished. asTypeId returns the inner TypeId;
|
|
// asInt MUST return null (no coercion).
|
|
try std.testing.expectEqual(@as(?TypeId, .i64), result.asTypeId());
|
|
try std.testing.expectEqual(@as(?i64, null), result.asInt());
|
|
}
|
|
|
|
// ── Test: type equality via cmp_eq on .type_tag operands ────────────────
|
|
|
|
test "comptime: type_tag comparison" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
// Returns (i64 == i64) — should yield bool true via the new
|
|
// evalCmp arm for .type_tag operands.
|
|
_ = b.beginFunction(str(&module, "test_type_eq_true"), &.{}, .bool);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const a = b.constType(.i64);
|
|
const c = b.constType(.i64);
|
|
const eq = b.cmpEq(a, c);
|
|
b.ret(eq, .bool);
|
|
b.finalize();
|
|
|
|
// Different TypeIds: (i64 == i32) should be false.
|
|
_ = b.beginFunction(str(&module, "test_type_eq_false"), &.{}, .bool);
|
|
const entry2 = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry2);
|
|
const a2 = b.constType(.i64);
|
|
const c2 = b.constType(.i32);
|
|
const eq2 = b.cmpEq(a2, c2);
|
|
b.ret(eq2, .bool);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const r_true = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(true, r_true.asBool().?);
|
|
const r_false = try interp.call(FuncId.fromIndex(1), &.{});
|
|
try std.testing.expectEqual(false, r_false.asBool().?);
|
|
}
|
|
|
|
// ── Test: type_name builtin reads .type_tag, returns the typeName ───────
|
|
|
|
test "comptime: type_name builtin on type_tag" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_type_name"), &.{}, .string);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const t = b.constType(.i64);
|
|
var args = [_]inst_mod.Ref{t};
|
|
const r = b.callBuiltin(.type_name, &args, .string);
|
|
b.ret(r, .string);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const result = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqualStrings("i64", result.asString(&interp).?);
|
|
}
|
|
|
|
// ── Test: type_eq builtin on two .type_tag operands ────────────────────
|
|
|
|
test "comptime: type_eq builtin on type_tag values" {
|
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
var module = Module.init(alloc);
|
|
defer module.deinit();
|
|
var b = Builder.init(&module);
|
|
|
|
_ = b.beginFunction(str(&module, "test_type_eq_builtin"), &.{}, .bool);
|
|
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
|
b.switchToBlock(entry);
|
|
const a = b.constType(.string);
|
|
const c = b.constType(.string);
|
|
var args = [_]inst_mod.Ref{ a, c };
|
|
const r = b.callBuiltin(.type_eq, &args, .bool);
|
|
b.ret(r, .bool);
|
|
b.finalize();
|
|
|
|
var interp = Interpreter.init(&module, alloc);
|
|
defer interp.deinit();
|
|
const result = try interp.call(FuncId.fromIndex(0), &.{});
|
|
try std.testing.expectEqual(true, result.asBool().?);
|
|
}
|
|
|
|
// ── Test: reflectTypeId reads an Any's runtime TYPE-TAG, not its payload ──
|
|
// A reflection builtin on an Any must report the type OF a held value (the
|
|
// tag) and only read the payload when the Any holds a Type value (tag ==
|
|
// `.any`). Regression: a boxed value like
|
|
// `av : Any = 6` (`{ tag = i64, value = 6 }`) must resolve to `i64`, NOT
|
|
// `types[6]` (`u8`).
|
|
test "reflect: reflectTypeId branches on the Any tag" {
|
|
const any_idx: i64 = @intCast(TypeId.any.index());
|
|
|
|
// Native first-class Type value → the held TypeId directly.
|
|
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .type_tag = .u64 }).reflectTypeId());
|
|
|
|
// Any holding a VALUE: `{ tag = i64, value = 6 }` → i64 (the tag),
|
|
// never `types[6]` (u8). This is the bug the fix closes.
|
|
var held_value = [_]Value{ .{ .int = @intCast(TypeId.i64.index()) }, .{ .int = 6 } };
|
|
try std.testing.expectEqual(@as(?TypeId, .i64), (Value{ .aggregate = &held_value }).reflectTypeId());
|
|
|
|
// Any holding a VALUE of an unsigned type: `{ tag = u32, value = 7 }` → u32.
|
|
var held_u32 = [_]Value{ .{ .int = @intCast(TypeId.u32.index()) }, .{ .int = 7 } };
|
|
try std.testing.expectEqual(@as(?TypeId, .u32), (Value{ .aggregate = &held_u32 }).reflectTypeId());
|
|
|
|
// Any holding a TYPE value (the `type_of(x)` / `const_type` shape):
|
|
// `{ tag = .any, value = u64 }` → u64 (the payload). Payload as a plain
|
|
// int (the runtime box shape) ...
|
|
var held_type_int = [_]Value{ .{ .int = any_idx }, .{ .int = @intCast(TypeId.u64.index()) } };
|
|
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .aggregate = &held_type_int }).reflectTypeId());
|
|
|
|
// ... and payload as a `.type_tag` (the comptime box shape) → same result.
|
|
var held_type_tag = [_]Value{ .{ .int = any_idx }, .{ .type_tag = .u64 } };
|
|
try std.testing.expectEqual(@as(?TypeId, .u64), (Value{ .aggregate = &held_type_tag }).reflectTypeId());
|
|
|
|
// Neither shape → null (the caller bails loudly, never guesses a TypeId).
|
|
try std.testing.expectEqual(@as(?TypeId, null), (Value{ .int = 6 }).reflectTypeId());
|
|
}
|