refactor(ir): extract ProgramIndex, move low-fanout decl facts (A1.1a)
Architecture phase A1.1a. Introduce src/ir/program_index.zig as the single storage owner for declaration-name / import / visibility facts, and move the three low-fanout maps out of the Lowering state bag: - import_flags (owned by ProgramIndex) - module_scopes (borrowed pointer into a core.zig-owned map) - import_graph (borrowed pointer into a core.zig-owned map) Lowering embeds one ProgramIndex by value and reaches every moved fact through self.program_index.<field>; later phases hand collaborator modules a *ProgramIndex instead of *Lowering. 8 call sites in lower.zig + 2 setters in core.zig repointed. No duplicate storage, no fallback path; zig build enforces no missed reference. Mutation-heavy registration (registerStructDecl etc.) stays in Lowering and now writes import_flags through the index. High-fanout maps are deferred to A1.1b. Adds src/ir/program_index.test.zig (init-empty, import_flags round-trip, borrowed-view ownership) wired into the ir.zig barrel. Behavior-preserving: zig build, zig build test, and bash tests/run_examples.sh (350 passed, 0 failed) all green.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.<field>`; 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);
|
||||
}
|
||||
}
|
||||
|
||||
40
src/ir/program_index.test.zig
Normal file
40
src/ir/program_index.test.zig
Normal file
@@ -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());
|
||||
}
|
||||
36
src/ir/program_index.zig
Normal file
36
src/ir/program_index.zig
Normal file
@@ -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();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user