test: make zig build test actually run all tests + fix latent rot
root.zig had no `test` block, so the test binary discovered zero tests and trivially "passed" — every src test had silently rotted. Add `refAllDecls(@This())` to root.zig so all 185 tests run, then fix the rot it surfaced: - emit_llvm.test: operands were constants, so LLVM folded the very instructions being asserted (fadd/sub/icmp/insertvalue/extractvalue/sext). Rewrite to use function-parameter operands; `main` now returns i32 (entry convention); tagged-union enum_init lowers via memory, not insertvalue. - interp.test: switch the per-test allocator to an arena (the interpreter is arena-style and intentionally frees little) — clears the transient-Value leaks without an ownership-ambiguous source change. - lower.test: pass `is_imported` to lowerFunction; mark two helpers `pub`; the if/else block test now uses a runtime (param) condition since lowering folds `if true`. - print.test: SSA numbering — params occupy %0/%1, so consts start at %2. - jni_java_emit.test: nested-class refs render in Java source form (`SurfaceHolder.Callback`), not the JNI `$` form. Leaks fixed at the source where ownership was clear: Module gains an arena for the operand slices the Builder dupes (struct/call/branch/switch args, block params, lowerFunction params); objcDefinedStateStructType builds its field slice in that arena and frees its temp name string.
This commit is contained in:
@@ -48,7 +48,9 @@ test "emit: main() returns 42" {
|
||||
// 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);
|
||||
// `main` is emitted with the C entry-point convention: it returns i32, so
|
||||
// the s64 const 42 is truncated to `ret i32 42`.
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "ret i32 42") != null);
|
||||
}
|
||||
|
||||
test "emit: add(a, b) returns a + b" {
|
||||
@@ -100,12 +102,17 @@ test "emit: float arithmetic" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "fmath"), &.{}, .f64);
|
||||
// Operands must be non-constant (function params) or LLVM constant-folds
|
||||
// the arithmetic away and no fadd/fmul instruction is emitted.
|
||||
_ = b.beginFunction(str(&module, "fmath"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "x"), .ty = .f64 },
|
||||
.{ .name = str(&module, "y"), .ty = .f64 },
|
||||
}, .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 a = Ref.fromIndex(0);
|
||||
const a_b = Ref.fromIndex(1);
|
||||
const sum = b.add(a, a_b, .f64);
|
||||
const product = b.mul(sum, a_b, .f64);
|
||||
b.ret(product, .f64);
|
||||
@@ -129,11 +136,14 @@ test "emit: negation" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "negate"), &.{}, .s64);
|
||||
// Negating a constant folds; negate a param so `sub 0, %x` is emitted.
|
||||
_ = b.beginFunction(str(&module, "negate"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "x"), .ty = .s64 },
|
||||
}, .s64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(7, .s64);
|
||||
const val = Ref.fromIndex(0);
|
||||
const neg = b.emit(.{ .neg = .{ .operand = val } }, .s64);
|
||||
b.ret(neg, .s64);
|
||||
b.finalize();
|
||||
@@ -211,15 +221,19 @@ test "emit: comparison and branch" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { if (10 < 20) return 1; else return 0; }
|
||||
_ = b.beginFunction(str(&module, "cmpfn"), &.{}, .s64);
|
||||
// func f(a, b) -> s64 { if (a < b) return 1; else return 0; }
|
||||
// Params (not constants) so the icmp isn't folded.
|
||||
_ = b.beginFunction(str(&module, "cmpfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "a"), .ty = .s64 },
|
||||
.{ .name = str(&module, "b"), .ty = .s64 },
|
||||
}, .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 a = Ref.fromIndex(0);
|
||||
const b_val = Ref.fromIndex(1);
|
||||
const cond = b.cmpLt(a, b_val);
|
||||
b.condBr(cond, then_bb, &.{}, else_bb, &.{});
|
||||
|
||||
@@ -291,11 +305,14 @@ test "emit: widen conversion s32 to s64" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "wfn"), &.{}, .s64);
|
||||
// sext of a constant folds; widen a param so `sext` is emitted.
|
||||
_ = b.beginFunction(str(&module, "wfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "x"), .ty = .s32 },
|
||||
}, .s64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(42, .s32);
|
||||
const val = Ref.fromIndex(0);
|
||||
const wide = b.widen(val, .s32, .s64);
|
||||
b.ret(wide, .s64);
|
||||
b.finalize();
|
||||
@@ -357,12 +374,16 @@ test "emit: struct_init and struct_get" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { p = Point{10, 20}; return p.y; }
|
||||
_ = b.beginFunction(str(&module, "f"), &.{}, .s64);
|
||||
// func f(v) -> s64 { p = Point{v, 20}; return p.y; }
|
||||
// A param operand keeps the aggregate non-constant so insertvalue /
|
||||
// extractvalue survive (a fully-constant struct would be folded).
|
||||
_ = b.beginFunction(str(&module, "f"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const x = b.constInt(10, .s64);
|
||||
const x = Ref.fromIndex(0);
|
||||
const y = b.constInt(20, .s64);
|
||||
const p = b.structInit(&.{ x, y }, point_ty);
|
||||
const py = b.structGet(p, 1, .s64);
|
||||
@@ -489,12 +510,15 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" {
|
||||
|
||||
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);
|
||||
// func f(r) -> f64 { s = Shape.Circle(r); ...; return payload; }
|
||||
// Param payload keeps the union value non-constant (else folded).
|
||||
_ = b.beginFunction(str(&module, "unionfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "r"), .ty = .f64 },
|
||||
}, .f64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const radius = b.constFloat(3.14, .f64);
|
||||
const radius = Ref.fromIndex(0);
|
||||
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
|
||||
@@ -509,7 +533,11 @@ test "emit: tagged union (enum_init with payload, enum_tag, enum_payload)" {
|
||||
try std.testing.expect(emitter.verify());
|
||||
|
||||
const ir_str = emitter.dumpToString();
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "insertvalue") != null);
|
||||
// Tagged-union enum_init/enum_payload lower to a memory pattern
|
||||
// (alloca + GEP + store/load), not SSA insert/extractvalue. enum_tag
|
||||
// does emit extractvalue.
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "alloca") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "getelementptr") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, ir_str, "extractvalue") != null);
|
||||
}
|
||||
|
||||
@@ -596,12 +624,14 @@ test "emit: length on slice" {
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f(s: string) -> s64 { return s.len; }
|
||||
_ = b.beginFunction(str(&module, "strlen"), &.{}, .s64);
|
||||
// A string param keeps the value non-constant so extractvalue survives.
|
||||
_ = b.beginFunction(str(&module, "strlen"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "s"), .ty = .string },
|
||||
}, .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 s = Ref.fromIndex(0);
|
||||
const len = b.emit(.{ .length = .{ .operand = s } }, .s64);
|
||||
b.ret(len, .s64);
|
||||
b.finalize();
|
||||
@@ -626,12 +656,15 @@ test "emit: data_ptr on slice" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> *u8 { s = "hello"; return s.ptr; }
|
||||
_ = b.beginFunction(str(&module, "dptr"), &.{}, ptr_ty);
|
||||
// func f(s: string) -> *u8 { return s.ptr; }
|
||||
// Param string → extractvalue survives (a constant string would fold).
|
||||
_ = b.beginFunction(str(&module, "dptr"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "s"), .ty = .string },
|
||||
}, ptr_ty);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const s = b.constString(str(&module, "test"));
|
||||
const s = Ref.fromIndex(0);
|
||||
const ptr = b.emit(.{ .data_ptr = .{ .operand = s } }, ptr_ty);
|
||||
b.ret(ptr, ptr_ty);
|
||||
b.finalize();
|
||||
@@ -688,14 +721,20 @@ test "emit: subslice" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> []u8 { s = "hello"; return s[1..3]; }
|
||||
_ = b.beginFunction(str(&module, "ssfn"), &.{}, slice_ty);
|
||||
// func f(s: []u8, lo: s64, hi: s64) -> []u8 { return s[lo..hi]; }
|
||||
// All operands are params: a constant base folds the GEP, and constant
|
||||
// lo/hi fold the `hi - lo` subtraction.
|
||||
_ = b.beginFunction(str(&module, "ssfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "s"), .ty = slice_ty },
|
||||
.{ .name = str(&module, "lo"), .ty = .s64 },
|
||||
.{ .name = str(&module, "hi"), .ty = .s64 },
|
||||
}, 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 s = Ref.fromIndex(0);
|
||||
const lo = Ref.fromIndex(1);
|
||||
const hi = Ref.fromIndex(2);
|
||||
const sub = b.emit(.{ .subslice = .{ .base = s, .lo = lo, .hi = hi } }, slice_ty);
|
||||
b.ret(sub, slice_ty);
|
||||
b.finalize();
|
||||
@@ -724,12 +763,15 @@ test "emit: optional_wrap and optional_unwrap (value type)" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { opt = wrap(42); return unwrap(opt); }
|
||||
_ = b.beginFunction(str(&module, "optfn"), &.{}, .s64);
|
||||
// func f(v) -> s64 { opt = wrap(v); return unwrap(opt); }
|
||||
// Param value keeps the optional non-constant (else insertvalue folds).
|
||||
_ = b.beginFunction(str(&module, "optfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(42, .s64);
|
||||
const val = Ref.fromIndex(0);
|
||||
const wrapped = b.optionalWrap(val, opt_ty);
|
||||
const unwrapped = b.optionalUnwrap(wrapped, .s64);
|
||||
b.ret(unwrapped, .s64);
|
||||
@@ -757,11 +799,14 @@ test "emit: optional_has_value" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
_ = b.beginFunction(str(&module, "hasfn"), &.{}, .bool);
|
||||
// Param value keeps the optional non-constant (else extractvalue folds).
|
||||
_ = b.beginFunction(str(&module, "hasfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .bool);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(10, .s64);
|
||||
const val = Ref.fromIndex(0);
|
||||
const wrapped = b.optionalWrap(val, opt_ty);
|
||||
const has = b.optionalHasValue(wrapped);
|
||||
b.ret(has, .bool);
|
||||
@@ -849,12 +894,17 @@ test "emit: closure_create" {
|
||||
b.ret(b.constInt(0, .s64), .s64);
|
||||
b.finalize();
|
||||
|
||||
// func f() -> closure { return closure_create(tramp, null); }
|
||||
_ = b.beginFunction(str(&module, "mkclose"), &.{}, closure_ty);
|
||||
// func f(e: *void) -> closure { return closure_create(tramp, e); }
|
||||
// A non-constant env keeps the {fn_ptr, env} aggregate non-constant so
|
||||
// the insertvalue isn't folded (a null env + constant fn_ptr would fold).
|
||||
const env_ty = module.types.ptrTo(.void);
|
||||
_ = b.beginFunction(str(&module, "mkclose"), &[_]inst_mod.Function.Param{
|
||||
.{ .name = str(&module, "e"), .ty = env_ty },
|
||||
}, 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);
|
||||
const cl = b.emit(.{ .closure_create = .{ .func = tramp_id, .env = Ref.fromIndex(0) } }, closure_ty);
|
||||
b.ret(cl, closure_ty);
|
||||
b.finalize();
|
||||
|
||||
@@ -877,12 +927,15 @@ test "emit: box_any and unbox_any" {
|
||||
|
||||
var b = Builder.init(&module);
|
||||
|
||||
// func f() -> s64 { a = box(42); return unbox(a); }
|
||||
_ = b.beginFunction(str(&module, "anyfn"), &.{}, .s64);
|
||||
// func f(v) -> s64 { a = box(v); return unbox(a); }
|
||||
// Param value keeps the boxed Any non-constant (else insertvalue folds).
|
||||
_ = b.beginFunction(str(&module, "anyfn"), &[_]Function.Param{
|
||||
.{ .name = str(&module, "v"), .ty = .s64 },
|
||||
}, .s64);
|
||||
const entry = b.appendBlock(str(&module, "entry"), &.{});
|
||||
b.switchToBlock(entry);
|
||||
|
||||
const val = b.constInt(42, .s64);
|
||||
const val = Ref.fromIndex(0);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user