fix(KB-9): move Lowering/ProgramIndex maps off page_allocator defaults

16 Lowering map fields and 8 ProgramIndex map fields were declared with
`= ....init(std.heap.page_allocator)` field defaults that init() never
replaced — every instance really allocated page-at-a-time outside the
compilation allocator, invisible to leak checking and never reclaimed.

All 24 now init explicitly with the compilation allocator (module.alloc
/ the init alloc param), which is arena-backed in both the driver
(main's arena) and the test suites (per-test arenas), so backing is
reclaimed at teardown. ProgramIndex's struct doc no longer claims the
page_allocator defaults.

Six lower.test.zig tests that constructed Module with bare
std.testing.allocator leaked once the checker could finally see these
maps; they now use the same per-test ArenaAllocator idiom as the rest
of the file and the facade test suites.

Gate: zig build OK; zig build test 426/426 (6/6 steps, leak-clean);
run_examples 541/0; zero expected/ snapshot churn.
This commit is contained in:
agra
2026-06-10 16:19:52 +03:00
parent 82500931ce
commit 5b304a29c1
3 changed files with 69 additions and 33 deletions

View File

@@ -464,7 +464,9 @@ test "lower: objcDefinedStateStructType skips non-field members" {
}
test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
@@ -496,7 +498,9 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
}
test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
@@ -663,7 +667,9 @@ test "lower: deriveObjcSelector — niladic / keyword / multi-keyword / override
}
test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
@@ -710,7 +716,9 @@ test "lower: isObjcClassPointer recognises pointer-to-foreign-Obj-C-class" {
}
test "lower: objcPropertyKind defaults + explicit ARC modifiers" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
@@ -783,7 +791,9 @@ fn protoMethod(name: []const u8) ast.ProtocolMethodDecl {
}
test "pack projection: type-arg vs method namespace lookups" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);
@@ -808,7 +818,9 @@ test "pack projection: type-arg vs method namespace lookups" {
}
test "pack projection: position-driven resolution (Decision 4)" {
const alloc = std.testing.allocator;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var lowering = Lowering.init(&module);

View File

@@ -288,21 +288,21 @@ pub const Lowering = struct {
/// visible ONLY within the source that declares it: an imported template's field
/// resolution (run in the template's source context, E3 attempt-4) must NOT bind a
/// name the CALLER declared block-local (E3 attempt-5).
local_type_names: std.StringHashMap(std.StringHashMap(void)) = std.StringHashMap(std.StringHashMap(void)).init(std.heap.page_allocator),
struct_defaults_map: std.StringHashMap([]const ?*const Node) = std.StringHashMap([]const ?*const Node).init(std.heap.page_allocator), // struct name → field defaults
struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)) = std.StringHashMap(std.StringHashMap(TypeId)).init(std.heap.page_allocator), // mangled struct name → type param bindings
struct_instance_template: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // mangled struct name → template name
struct_instance_author: std.StringHashMap(*const ast.StructDecl) = std.StringHashMap(*const ast.StructDecl).init(std.heap.page_allocator), // mangled struct name → authoring StructDecl (CP-2: body-author ≡ layout-author)
local_type_names: std.StringHashMap(std.StringHashMap(void)),
struct_defaults_map: std.StringHashMap([]const ?*const Node), // struct name → field defaults
struct_instance_bindings: std.StringHashMap(std.StringHashMap(TypeId)), // mangled struct name → type param bindings
struct_instance_template: std.StringHashMap([]const u8), // mangled struct name → template name
struct_instance_author: std.StringHashMap(*const ast.StructDecl), // mangled struct name → authoring StructDecl (CP-2: body-author ≡ layout-author)
comptime_value_bindings: ?std.StringHashMap(i64) = null, // comptime value bindings ($N → integer value)
protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds
protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId
protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId) = std.StringHashMap(inst_mod.GlobalId).init(std.heap.page_allocator), // "Proto\x00Type" → vtable GlobalId
param_impl_map: std.StringHashMap(std.ArrayList(ParamImplEntry)) = std.StringHashMap(std.ArrayList(ParamImplEntry)).init(std.heap.page_allocator), // "Proto\x00<arg_mangled>\x00<src_mangled>" → impl entries (parameterised protocols only; list lets Phase 4/5 detect cross-module overlap)
protocol_thunk_map: std.StringHashMap([]const FuncId), // "Proto\x00Type" → thunk FuncIds
protocol_vtable_type_map: std.StringHashMap(TypeId), // protocol name → vtable struct TypeId
protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId), // "Proto\x00Type" → vtable GlobalId
param_impl_map: std.StringHashMap(std.ArrayList(ParamImplEntry)), // "Proto\x00<arg_mangled>\x00<src_mangled>" → impl entries (parameterised protocols only; list lets Phase 4/5 detect cross-module overlap)
/// Pack-variadic impl entries — separate map keyed by `"Proto\x00<arg_mangled>"`
/// (NO source suffix) so a single impl `Closure(..$args) -> $R` can be
/// matched against many concrete source shapes. Concrete impls in
/// `param_impl_map` win when both match (specificity rule).
param_impl_pack_map: std.StringHashMap(std.ArrayList(PackParamImplEntry)) = std.StringHashMap(std.ArrayList(PackParamImplEntry)).init(std.heap.page_allocator),
param_impl_pack_map: std.StringHashMap(std.ArrayList(PackParamImplEntry)),
/// Active pack bindings during monomorphisation. Mirrors `type_bindings`
/// but for variadic pack names: `args → [T1, T2, ...]`. Read by
/// `resolveTypeWithBindings` on closure_type_expr to substitute
@@ -346,26 +346,26 @@ pub const Lowering = struct {
/// `xs[i].<m>` is rejected unless `<m>` is one of the protocol's methods.
/// Null / absent for the comptime `..$args` pack (no constraint).
pack_constraint: ?std.StringHashMap([]const u8) = null,
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
struct_const_map: std.StringHashMap(StructConstInfo), // "Struct.CONST" → value info
foreign_name_map: std.StringHashMap([]const u8), // sx name → C name for #foreign renames
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
comptime_constants: std.StringHashMap(ComptimeValue), // compile-time known constants (e.g. OS, ARCH)
diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations
xx_reentrancy: std.AutoHashMap(u64, void) = std.AutoHashMap(u64, void).init(std.heap.page_allocator), // (src_ty, dst_ty) pairs currently being resolved through user-space Into; prevents infinite monomorphisation when a convert body re-enters the same xx
xx_reentrancy: std.AutoHashMap(u64, void), // (src_ty, dst_ty) pairs currently being resolved through user-space Into; prevents infinite monomorphisation when a convert body re-enters the same xx
/// Whole-program-converged inferred error sets (ERR E1.4b): top-level
/// bare-`!` function name → its sorted escape-tag ids (literal raises +
/// pure-failable `try` edges, fix-pointed across the call graph). The
/// shared `!` placeholder TypeId stays empty; this side map holds the real
/// per-function sets (sidesteps the name-only error-set interning). Read by
/// `lowerTry`'s named-caller widening and the empty-inferred warning.
inferred_error_sets: std.StringHashMap([]const u32) = std.StringHashMap([]const u32).init(std.heap.page_allocator),
inferred_error_sets: std.StringHashMap([]const u32),
/// Whole-program-converged inferred error sets keyed by closure/function
/// VALUE-signature shape (ERR E5.1 sub-feature 2): every occurrence of
/// `Closure(<sig>) -> (T, !)` with a structurally identical value-signature
/// shares one node; each bare-`!` closure literal of that shape unions its
/// escape tags in. Read by `checkEscapeWidening` when a `try` operand is a
/// closure/fn-type SLOT call (no static fn name). Key = `closureShapeKey`.
shape_inferred_sets: std.StringHashMap([]const u32) = std.StringHashMap([]const u32).init(std.heap.page_allocator),
shape_inferred_sets: std.StringHashMap([]const u32),
pub const ComptimeValue = union(enum) {
int_val: i64,
@@ -496,6 +496,22 @@ pub const Lowering = struct {
.fn_decl_fids = std.AutoHashMap(*const ast.FnDecl, FuncId).init(module.alloc),
.lowered_fids = std.AutoHashMap(FuncId, void).init(module.alloc),
.nominal_name_authors = std.AutoHashMap(types.StringId, []const u8).init(module.alloc),
.local_type_names = std.StringHashMap(std.StringHashMap(void)).init(module.alloc),
.struct_defaults_map = std.StringHashMap([]const ?*const Node).init(module.alloc),
.struct_instance_bindings = std.StringHashMap(std.StringHashMap(TypeId)).init(module.alloc),
.struct_instance_template = std.StringHashMap([]const u8).init(module.alloc),
.struct_instance_author = std.StringHashMap(*const ast.StructDecl).init(module.alloc),
.protocol_thunk_map = std.StringHashMap([]const FuncId).init(module.alloc),
.protocol_vtable_type_map = std.StringHashMap(TypeId).init(module.alloc),
.protocol_vtable_global_map = std.StringHashMap(inst_mod.GlobalId).init(module.alloc),
.param_impl_map = std.StringHashMap(std.ArrayList(ParamImplEntry)).init(module.alloc),
.param_impl_pack_map = std.StringHashMap(std.ArrayList(PackParamImplEntry)).init(module.alloc),
.struct_const_map = std.StringHashMap(StructConstInfo).init(module.alloc),
.foreign_name_map = std.StringHashMap([]const u8).init(module.alloc),
.comptime_constants = std.StringHashMap(ComptimeValue).init(module.alloc),
.xx_reentrancy = std.AutoHashMap(u64, void).init(module.alloc),
.inferred_error_sets = std.StringHashMap([]const u32).init(module.alloc),
.shape_inferred_sets = std.StringHashMap([]const u32).init(module.alloc),
.program_index = ProgramIndex.init(module.alloc),
};
}

View File

@@ -592,9 +592,9 @@ pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
/// (pointers into maps owned by the compilation driver, `core.zig`) — those are
/// read-only views and are never freed here.
///
/// Per-map allocators are preserved exactly as they were on `Lowering`:
/// `import_flags` / `fn_ast_map` / `global_names` use the lowering allocator
/// (set in `init`); the rest default to `page_allocator`. Written only by the
/// Every owned map allocates through the compilation allocator passed to
/// `init` (arena-backed in both the driver and the tests — KB-9 removed the
/// old `page_allocator` field defaults). Written only by the
/// declaration scan / registration code in `Lowering`; read everywhere else.
pub const ProgramIndex = struct {
/// The lowering/compilation allocator (`module.alloc`), retained so the
@@ -639,27 +639,27 @@ pub const ProgramIndex = struct {
/// resolve (issue 0100 F1). Keyed/allocated with the lowering allocator.
qualified_fn_source: std.StringHashMap([]const u8),
/// sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass).
foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator),
foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl),
/// `#run` global name → GlobalId.
global_names: std.StringHashMap(GlobalInfo),
/// Type alias name → target TypeId. The single-source alias table; passed
/// explicitly to `TypeResolver` / `type_bridge` resolution (no borrow).
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator),
type_alias_map: std.StringHashMap(TypeId),
/// Generic struct name → template.
struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator),
struct_template_map: std.StringHashMap(StructTemplate),
/// `DeclId` → generic struct template — the DeclId-keyed analogue of
/// `struct_template_map`, built in parallel during `registerStructDecl`.
/// Nothing reads it for selection yet; `struct_template_map` stays the live
/// consumer until the S4 cutover.
struct_template_by_decl: std.AutoHashMap(imports.DeclId, StructTemplate) = std.AutoHashMap(imports.DeclId, StructTemplate).init(std.heap.page_allocator),
struct_template_by_decl: std.AutoHashMap(imports.DeclId, StructTemplate),
/// Protocol name → protocol info.
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator),
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo),
/// Protocol name → AST node.
protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator),
protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl),
/// Module-level value constants (e.g. AF_INET :s32: 2).
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator),
module_const_map: std.StringHashMap(ModuleConstInfo),
/// UFCS alias name → target function name.
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator),
ufcs_alias_map: std.StringHashMap([]const u8),
// ── Source-keyed semantic caches (R5 §#4) ──
// The source-partitioned analogues of `type_alias_map` / `module_const_map`
@@ -682,6 +682,14 @@ pub const ProgramIndex = struct {
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc),
.qualified_fn_source = std.StringHashMap([]const u8).init(alloc),
.global_names = std.StringHashMap(GlobalInfo).init(alloc),
.foreign_class_map = std.StringHashMap(*const ast.ForeignClassDecl).init(alloc),
.type_alias_map = std.StringHashMap(TypeId).init(alloc),
.struct_template_map = std.StringHashMap(StructTemplate).init(alloc),
.struct_template_by_decl = std.AutoHashMap(imports.DeclId, StructTemplate).init(alloc),
.protocol_decl_map = std.StringHashMap(ProtocolDeclInfo).init(alloc),
.protocol_ast_map = std.StringHashMap(*const ast.ProtocolDecl).init(alloc),
.module_const_map = std.StringHashMap(ModuleConstInfo).init(alloc),
.ufcs_alias_map = std.StringHashMap([]const u8).init(alloc),
.type_aliases_by_source = std.StringHashMap(std.StringHashMap(TypeId)).init(alloc),
.module_consts_by_source = std.StringHashMap(std.StringHashMap(ModuleConstInfo)).init(alloc),
.globals_by_source = std.StringHashMap(std.StringHashMap(GlobalInfo)).init(alloc),