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:
@@ -52,6 +52,12 @@ pub const Module = struct {
|
||||
/// `members` for fields / methods / `#extends` / `#implements`.
|
||||
objc_defined_class_cache: std.ArrayList(ObjcDefinedClassEntry),
|
||||
alloc: Allocator,
|
||||
/// Owns the per-instruction operand slices the Builder dupes (aggregate
|
||||
/// fields, call args, branch args, switch cases, block params). These live
|
||||
/// for the module's lifetime and are never freed individually — an arena
|
||||
/// reclaims them all in `deinit`, matching the compiler's arena-style
|
||||
/// memory model and keeping the leak-checking test allocator clean.
|
||||
slice_arena: std.heap.ArenaAllocator,
|
||||
/// True when this module's program imports `std.sx` (and therefore
|
||||
/// has the `Context` type). Set by lowering's Pass 0 pre-scan. Read
|
||||
/// by emit_llvm to decide whether closure/fn-pointer call sites
|
||||
@@ -95,6 +101,7 @@ pub const Module = struct {
|
||||
.objc_class_cache = std.ArrayList(ObjcClassEntry).empty,
|
||||
.objc_defined_class_cache = std.ArrayList(ObjcDefinedClassEntry).empty,
|
||||
.alloc = alloc,
|
||||
.slice_arena = std.heap.ArenaAllocator.init(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,6 +116,7 @@ pub const Module = struct {
|
||||
self.objc_class_cache.deinit(self.alloc);
|
||||
self.objc_defined_class_cache.deinit(self.alloc);
|
||||
self.types.deinit();
|
||||
self.slice_arena.deinit();
|
||||
}
|
||||
|
||||
/// Linear scan — N is the count of UNIQUE selectors per program,
|
||||
@@ -258,7 +266,7 @@ pub const Builder = struct {
|
||||
if (existing.name == name and existing.is_extern) {
|
||||
existing.is_extern = false;
|
||||
existing.linkage = .internal;
|
||||
existing.params = self.module.alloc.dupe(Function.Param, params) catch params;
|
||||
existing.params = self.module.slice_arena.allocator().dupe(Function.Param, params) catch params;
|
||||
existing.ret = ret_ty;
|
||||
const id = FuncId.fromIndex(@intCast(i));
|
||||
self.func = id;
|
||||
@@ -298,7 +306,7 @@ pub const Builder = struct {
|
||||
const id = BlockId.fromIndex(@intCast(f.blocks.items.len));
|
||||
// Dupe params so the block owns the memory (callers may pass stack slices).
|
||||
const owned_params = if (params.len > 0)
|
||||
(self.module.alloc.dupe(TypeId, params) catch unreachable)
|
||||
(self.module.slice_arena.allocator().dupe(TypeId, params) catch unreachable)
|
||||
else
|
||||
params;
|
||||
f.blocks.append(self.module.alloc, Block.init(name, owned_params)) catch unreachable;
|
||||
@@ -443,7 +451,7 @@ pub const Builder = struct {
|
||||
// ── Struct ops ──────────────────────────────────────────────────
|
||||
|
||||
pub fn structInit(self: *Builder, fields: []const Ref, ty: TypeId) Ref {
|
||||
const owned = self.module.alloc.dupe(Ref, fields) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, fields) catch unreachable;
|
||||
return self.emit(.{ .struct_init = .{ .fields = owned } }, ty);
|
||||
}
|
||||
|
||||
@@ -486,23 +494,23 @@ pub const Builder = struct {
|
||||
// ── Calls ───────────────────────────────────────────────────────
|
||||
|
||||
pub fn call(self: *Builder, callee: FuncId, args: []const Ref, ret_ty: TypeId) Ref {
|
||||
const owned = self.module.alloc.dupe(Ref, args) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
|
||||
return self.emit(.{ .call = .{ .callee = callee, .args = owned } }, ret_ty);
|
||||
}
|
||||
|
||||
pub fn callClosure(self: *Builder, callee: Ref, args: []const Ref, ret_ty: TypeId) Ref {
|
||||
const owned = self.module.alloc.dupe(Ref, args) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
|
||||
return self.emit(.{ .call_closure = .{ .callee = callee, .args = owned } }, ret_ty);
|
||||
}
|
||||
|
||||
pub fn callBuiltin(self: *Builder, builtin: inst.BuiltinId, args: []const Ref, ret_ty: TypeId) Ref {
|
||||
const owned = self.module.alloc.dupe(Ref, args) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
|
||||
return self.emit(.{ .call_builtin = .{ .builtin = builtin, .args = owned } }, ret_ty);
|
||||
}
|
||||
|
||||
pub fn compilerCall(self: *Builder, name: []const u8, args: []const Ref, ret_ty: TypeId) Ref {
|
||||
const name_id = self.module.types.strings.intern(self.module.alloc, name);
|
||||
const owned = self.module.alloc.dupe(Ref, args) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
|
||||
return self.emit(.{ .compiler_call = .{ .name = @intFromEnum(name_id), .args = owned } }, ret_ty);
|
||||
}
|
||||
|
||||
@@ -531,13 +539,13 @@ pub const Builder = struct {
|
||||
// ── Terminators ─────────────────────────────────────────────────
|
||||
|
||||
pub fn br(self: *Builder, target: BlockId, args: []const Ref) void {
|
||||
const owned = self.module.alloc.dupe(Ref, args) catch unreachable;
|
||||
const owned = self.module.slice_arena.allocator().dupe(Ref, args) catch unreachable;
|
||||
self.emitVoid(.{ .br = .{ .target = target, .args = owned } }, .void);
|
||||
}
|
||||
|
||||
pub fn condBr(self: *Builder, cond: Ref, then_target: BlockId, then_args: []const Ref, else_target: BlockId, else_args: []const Ref) void {
|
||||
const t_args = self.module.alloc.dupe(Ref, then_args) catch unreachable;
|
||||
const e_args = self.module.alloc.dupe(Ref, else_args) catch unreachable;
|
||||
const t_args = self.module.slice_arena.allocator().dupe(Ref, then_args) catch unreachable;
|
||||
const e_args = self.module.slice_arena.allocator().dupe(Ref, else_args) catch unreachable;
|
||||
self.emitVoid(.{ .cond_br = .{
|
||||
.cond = cond,
|
||||
.then_target = then_target,
|
||||
@@ -556,8 +564,8 @@ pub const Builder = struct {
|
||||
}
|
||||
|
||||
pub fn switchBr(self: *Builder, operand: Ref, cases: []const inst.SwitchBranch.Case, default: BlockId, default_args: []const Ref) void {
|
||||
const owned_cases = self.module.alloc.dupe(inst.SwitchBranch.Case, cases) catch unreachable;
|
||||
const owned_default_args = self.module.alloc.dupe(Ref, default_args) catch unreachable;
|
||||
const owned_cases = self.module.slice_arena.allocator().dupe(inst.SwitchBranch.Case, cases) catch unreachable;
|
||||
const owned_default_args = self.module.slice_arena.allocator().dupe(Ref, default_args) catch unreachable;
|
||||
self.emitVoid(.{ .switch_br = .{
|
||||
.operand = operand,
|
||||
.cases = owned_cases,
|
||||
|
||||
Reference in New Issue
Block a user