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:
agra
2026-05-29 15:25:00 +03:00
parent 92638ae9b5
commit 4defadf513
9 changed files with 243 additions and 108 deletions

View File

@@ -1216,17 +1216,21 @@ pub const Lowering = struct {
const wants_ctx = self.funcWantsImplicitCtx(fd);
// Build param list
// Build param list. `Function.init` borrows the slice (it does not
// dupe), so this storage must outlive the local — build it in the
// module's slice arena (freed at module deinit) rather than via
// `self.alloc`, which would leak (Function.deinit never frees params).
const param_alloc = self.module.slice_arena.allocator();
var params = std.ArrayList(Function.Param).empty;
if (wants_ctx) {
params.append(self.alloc, .{
params.append(param_alloc, .{
.name = self.module.types.internString("__sx_ctx"),
.ty = self.module.types.ptrTo(.void),
}) catch unreachable;
}
for (fd.params) |p| {
const pty = self.resolveParamType(&p);
params.append(self.alloc, .{
params.append(param_alloc, .{
.name = self.module.types.internString(p.name),
.ty = pty,
}) catch unreachable;
@@ -5054,7 +5058,7 @@ pub const Lowering = struct {
/// patterns rule).
///
/// Returns an allocator-owned slice; caller frees via `self.alloc`.
fn objcTypeEncodingFromSignature(
pub fn objcTypeEncodingFromSignature(
self: *Lowering,
return_ty: TypeId,
param_tys: []const TypeId,
@@ -5268,11 +5272,16 @@ pub const Lowering = struct {
/// Foreign-class members other than `.field` are ignored here —
/// methods / `#extends` / `#implements` don't contribute to the
/// state layout.
fn objcDefinedStateStructType(self: *Lowering, fcd: *const ast.ForeignClassDecl) TypeId {
pub fn objcDefinedStateStructType(self: *Lowering, fcd: *const ast.ForeignClassDecl) TypeId {
const state_name = std.fmt.allocPrint(self.alloc, "__{s}State", .{fcd.name}) catch unreachable;
defer self.alloc.free(state_name); // internString copies; the temp isn't needed after.
const name_id = self.module.types.internString(state_name);
if (self.module.types.findByName(name_id)) |existing| return existing;
// The interned struct's `fields` slice lives for the module's lifetime;
// allocate it (and the building ArrayList) in the module arena so it's
// freed at module deinit rather than leaking through `self.alloc`.
const field_alloc = self.module.slice_arena.allocator();
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
// M4.0: prepend __sx_allocator at field index 0 — captured at +alloc
// time, read at -dealloc time to free the state struct through the
@@ -5280,7 +5289,7 @@ pub const Lowering = struct {
// emitObjcDefinedClassPropertyImps + lookupObjcDefinedStateFieldOnPointer)
// naturally finds user fields at their post-shift indices.
if (self.objcStateAllocatorType()) |allocator_ty| {
fields.append(self.alloc, .{
fields.append(field_alloc, .{
.name = self.module.types.internString("__sx_allocator"),
.ty = allocator_ty,
}) catch unreachable;
@@ -5290,14 +5299,14 @@ pub const Lowering = struct {
.field => |f| {
const f_name_id = self.module.types.internString(f.name);
const f_ty = self.resolveType(f.field_type);
fields.append(self.alloc, .{ .name = f_name_id, .ty = f_ty }) catch unreachable;
fields.append(field_alloc, .{ .name = f_name_id, .ty = f_ty }) catch unreachable;
},
else => {},
}
}
return self.module.types.intern(.{ .@"struct" = .{
.name = name_id,
.fields = fields.toOwnedSlice(self.alloc) catch unreachable,
.fields = fields.toOwnedSlice(field_alloc) catch unreachable,
} });
}