diff --git a/src/core.zig b/src/core.zig index ffb524e..6a6c2ee 100644 --- a/src/core.zig +++ b/src/core.zig @@ -288,8 +288,8 @@ pub const Compilation = struct { lowering.resolved_root = root; lowering.target_config = self.target_config; lowering.diagnostics = &self.diagnostics; - lowering.module_scopes = &self.module_scopes; - lowering.import_graph = &self.import_graph; + lowering.program_index.module_scopes = &self.module_scopes; + lowering.program_index.import_graph = &self.import_graph; lowering.lowerRoot(root); if (self.diagnostics.hasErrors()) return error.CompileError; diff --git a/src/ir/ir.zig b/src/ir/ir.zig index d7a614c..fa24b05 100644 --- a/src/ir/ir.zig +++ b/src/ir/ir.zig @@ -4,6 +4,7 @@ pub const module = @import("module.zig"); pub const print = @import("print.zig"); pub const interp = @import("interp.zig"); pub const lower = @import("lower.zig"); +pub const program_index = @import("program_index.zig"); pub const TypeId = types.TypeId; pub const TypeInfo = types.TypeInfo; @@ -30,6 +31,7 @@ pub const printModule = print.printModule; pub const Interpreter = interp.Interpreter; pub const Value = interp.Value; pub const Lowering = lower.Lowering; +pub const ProgramIndex = program_index.ProgramIndex; pub const compiler_hooks = @import("compiler_hooks.zig"); pub const emit_llvm = @import("emit_llvm.zig"); @@ -48,6 +50,7 @@ pub const module_tests = @import("module.test.zig"); pub const print_tests = @import("print.test.zig"); pub const interp_tests = @import("interp.test.zig"); pub const lower_tests = @import("lower.test.zig"); +pub const program_index_tests = @import("program_index.test.zig"); pub const type_bridge_tests = @import("type_bridge.test.zig"); pub const emit_llvm_tests = @import("emit_llvm.test.zig"); pub const jni_descriptor_tests = @import("jni_descriptor.test.zig"); diff --git a/src/ir/lower.zig b/src/ir/lower.zig index f340c8b..e67e102 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11,6 +11,7 @@ const parser_mod = @import("../parser.zig"); const interp_mod = @import("interp.zig"); const errors = @import("../errors.zig"); const jni_descriptor = @import("jni_descriptor.zig"); +const ProgramIndex = @import("program_index.zig").ProgramIndex; const TypeId = types.TypeId; const StringId = types.StringId; @@ -104,9 +105,11 @@ pub const Lowering = struct { target_type: ?TypeId = null, // target type for struct/enum literals without explicit names lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered local_fn_counter: u32 = 0, // unique counter for mangling local function names - import_flags: std.StringHashMap(bool), // tracks whether each function is imported - module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution) - import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, // module path → set of directly imported paths (used by param_impl_map visibility filter) + /// Declaration-name / import / visibility facts (architecture phase A1, + /// `ProgramIndex`). Owns `import_flags`; borrows `module_scopes` / + /// `import_graph` from the compilation driver. Reached via + /// `self.program_index.`; populated by scan/registration code. + program_index: ProgramIndex, current_source_file: ?[]const u8 = null, // source file of function currently being lowered // Implicit Context parameter machinery. When the program imports // `std.sx` (and therefore declares `Context :: struct {...}`), every @@ -312,7 +315,7 @@ pub const Lowering = struct { .alloc = module.alloc, .fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(module.alloc), .lowered_functions = std.StringHashMap(void).init(module.alloc), - .import_flags = std.StringHashMap(bool).init(module.alloc), + .program_index = ProgramIndex.init(module.alloc), .global_names = std.StringHashMap(GlobalInfo).init(module.alloc), }; } @@ -1479,14 +1482,14 @@ pub const Lowering = struct { switch (decl.data) { .fn_decl => |fd| { self.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {}; - self.import_flags.put(fd.name, is_imported) catch {}; + self.program_index.import_flags.put(fd.name, is_imported) catch {}; // Declare extern stub for all functions (bodies lowered lazily) self.declareFunction(&fd, fd.name); }, .const_decl => |cd| { if (cd.value.data == .fn_decl) { self.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) catch {}; - self.import_flags.put(cd.name, is_imported) catch {}; + self.program_index.import_flags.put(cd.name, is_imported) catch {}; self.declareFunction(&cd.value.data.fn_decl, cd.name); } else if (cd.value.data == .struct_decl) { self.registerStructDecl(&cd.value.data.struct_decl); @@ -1893,11 +1896,11 @@ pub const Lowering = struct { /// responsible for restricting the call to names that ARE known /// top-level decls; otherwise every local variable would be policed. fn isNameVisible(self: *Lowering, name: []const u8) bool { - const scopes = self.module_scopes orelse return true; + const scopes = self.program_index.module_scopes orelse return true; const source = self.current_source_file orelse return true; const own_scope = scopes.get(source) orelse return true; if (own_scope.contains(name)) return true; - const graph = self.import_graph orelse return true; + const graph = self.program_index.import_graph orelse return true; const direct = graph.get(source) orelse return true; var it = direct.iterator(); while (it.next()) |kv| { @@ -11984,7 +11987,7 @@ pub const Lowering = struct { out.appendSlice(self.alloc, entries) catch {}; return; }; - const graph = self.import_graph orelse { + const graph = self.program_index.import_graph orelse { out.appendSlice(self.alloc, entries) catch {}; return; }; @@ -14360,7 +14363,7 @@ pub const Lowering = struct { const method_fd = &method_node.data.fn_decl; const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ib.target_type, method_fd.name }) catch continue; self.fn_ast_map.put(qualified, method_fd) catch {}; - self.import_flags.put(qualified, is_imported) catch {}; + self.program_index.import_flags.put(qualified, is_imported) catch {}; self.declareFunction(method_fd, qualified); impl_methods.put(method_fd.name, {}) catch {}; } @@ -14373,7 +14376,7 @@ pub const Lowering = struct { const synth_fd = self.synthesizeDefaultMethod(method, ib.target_type); const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ib.target_type, method.name }) catch continue; self.fn_ast_map.put(qualified, synth_fd) catch {}; - self.import_flags.put(qualified, is_imported) catch {}; + self.program_index.import_flags.put(qualified, is_imported) catch {}; self.declareFunction(synth_fd, qualified); } } @@ -14480,7 +14483,7 @@ pub const Lowering = struct { const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ src_name, mfd.name }) catch continue; if (self.fn_ast_map.contains(q)) continue; // first impl wins self.fn_ast_map.put(q, mfd) catch {}; - self.import_flags.put(q, is_imported) catch {}; + self.program_index.import_flags.put(q, is_imported) catch {}; if (!is_generic_src) self.declareFunction(mfd, q); } } diff --git a/src/ir/program_index.test.zig b/src/ir/program_index.test.zig new file mode 100644 index 0000000..22d502f --- /dev/null +++ b/src/ir/program_index.test.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const ProgramIndex = @import("program_index.zig").ProgramIndex; + +test "ProgramIndex.init starts empty with unset borrowed views" { + var idx = ProgramIndex.init(std.testing.allocator); + defer idx.deinit(); + try std.testing.expectEqual(@as(u32, 0), idx.import_flags.count()); + try std.testing.expect(idx.module_scopes == null); + try std.testing.expect(idx.import_graph == null); +} + +test "ProgramIndex.import_flags round-trips imported vs local" { + var idx = ProgramIndex.init(std.testing.allocator); + defer idx.deinit(); + try idx.import_flags.put("printf", true); + try idx.import_flags.put("main", false); + try std.testing.expectEqual(@as(?bool, true), idx.import_flags.get("printf")); + try std.testing.expectEqual(@as(?bool, false), idx.import_flags.get("main")); + try std.testing.expect(idx.import_flags.get("absent") == null); +} + +test "ProgramIndex borrows module_scopes / import_graph without owning them" { + const ScopeSet = std.StringHashMap(std.StringHashMap(void)); + var scopes = ScopeSet.init(std.testing.allocator); + defer scopes.deinit(); + var graph = ScopeSet.init(std.testing.allocator); + defer graph.deinit(); + + var idx = ProgramIndex.init(std.testing.allocator); + defer idx.deinit(); + idx.module_scopes = &scopes; + idx.import_graph = &graph; + + // Reads go through the borrowed pointer; the backing stays caller-owned, + // so idx.deinit() must not free it (testing.allocator would flag a + // double-free / leak otherwise). + try std.testing.expect(idx.module_scopes.? == &scopes); + try std.testing.expect(idx.import_graph.? == &graph); + try std.testing.expectEqual(@as(u32, 0), idx.module_scopes.?.count()); +} diff --git a/src/ir/program_index.zig b/src/ir/program_index.zig new file mode 100644 index 0000000..c5f4800 --- /dev/null +++ b/src/ir/program_index.zig @@ -0,0 +1,36 @@ +const std = @import("std"); + +/// Single storage owner for declaration-name facts: declarations, imports, +/// visibility, names, aliases, templates, protocols, foreign classes, and +/// module constants. The architecture stream (`current/PLAN-ARCH.md`, phase +/// A1) extracts these out of the `Lowering` state bag incrementally; this is +/// the A1.1a slice — the low-fanout import/visibility facts. `Lowering` embeds +/// one `ProgramIndex` by value and reaches every moved fact through it; later +/// phases hand collaborator modules a `*ProgramIndex` instead of `*Lowering`. +/// +/// `import_flags` is owned here. `module_scopes` / `import_graph` are borrowed +/// views into maps owned by the compilation driver (`core.zig`); this index +/// only reads through them. +pub const ProgramIndex = struct { + /// Declaration name → is the function imported (declared `extern`)? + /// Populated by the declaration scan / registration code in `Lowering`. + import_flags: std.StringHashMap(bool), + + /// Per-module visible names, keyed by source file (from import + /// resolution). Borrowed — the backing map lives in the compilation + /// driver, so this index does not free it. + module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, + + /// Module path → set of directly imported paths; drives the + /// `param_impl_map` cross-module visibility filter. Borrowed — owned by + /// the compilation driver. + import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, + + pub fn init(alloc: std.mem.Allocator) ProgramIndex { + return .{ .import_flags = std.StringHashMap(bool).init(alloc) }; + } + + pub fn deinit(self: *ProgramIndex) void { + self.import_flags.deinit(); + } +};