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.resolved_root = root;
|
||||||
lowering.target_config = self.target_config;
|
lowering.target_config = self.target_config;
|
||||||
lowering.diagnostics = &self.diagnostics;
|
lowering.diagnostics = &self.diagnostics;
|
||||||
lowering.module_scopes = &self.module_scopes;
|
lowering.program_index.module_scopes = &self.module_scopes;
|
||||||
lowering.import_graph = &self.import_graph;
|
lowering.program_index.import_graph = &self.import_graph;
|
||||||
lowering.lowerRoot(root);
|
lowering.lowerRoot(root);
|
||||||
if (self.diagnostics.hasErrors()) return error.CompileError;
|
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 print = @import("print.zig");
|
||||||
pub const interp = @import("interp.zig");
|
pub const interp = @import("interp.zig");
|
||||||
pub const lower = @import("lower.zig");
|
pub const lower = @import("lower.zig");
|
||||||
|
pub const program_index = @import("program_index.zig");
|
||||||
|
|
||||||
pub const TypeId = types.TypeId;
|
pub const TypeId = types.TypeId;
|
||||||
pub const TypeInfo = types.TypeInfo;
|
pub const TypeInfo = types.TypeInfo;
|
||||||
@@ -30,6 +31,7 @@ pub const printModule = print.printModule;
|
|||||||
pub const Interpreter = interp.Interpreter;
|
pub const Interpreter = interp.Interpreter;
|
||||||
pub const Value = interp.Value;
|
pub const Value = interp.Value;
|
||||||
pub const Lowering = lower.Lowering;
|
pub const Lowering = lower.Lowering;
|
||||||
|
pub const ProgramIndex = program_index.ProgramIndex;
|
||||||
|
|
||||||
pub const compiler_hooks = @import("compiler_hooks.zig");
|
pub const compiler_hooks = @import("compiler_hooks.zig");
|
||||||
pub const emit_llvm = @import("emit_llvm.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 print_tests = @import("print.test.zig");
|
||||||
pub const interp_tests = @import("interp.test.zig");
|
pub const interp_tests = @import("interp.test.zig");
|
||||||
pub const lower_tests = @import("lower.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 type_bridge_tests = @import("type_bridge.test.zig");
|
||||||
pub const emit_llvm_tests = @import("emit_llvm.test.zig");
|
pub const emit_llvm_tests = @import("emit_llvm.test.zig");
|
||||||
pub const jni_descriptor_tests = @import("jni_descriptor.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 interp_mod = @import("interp.zig");
|
||||||
const errors = @import("../errors.zig");
|
const errors = @import("../errors.zig");
|
||||||
const jni_descriptor = @import("jni_descriptor.zig");
|
const jni_descriptor = @import("jni_descriptor.zig");
|
||||||
|
const ProgramIndex = @import("program_index.zig").ProgramIndex;
|
||||||
|
|
||||||
const TypeId = types.TypeId;
|
const TypeId = types.TypeId;
|
||||||
const StringId = types.StringId;
|
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
|
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
|
lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered
|
||||||
local_fn_counter: u32 = 0, // unique counter for mangling local function names
|
local_fn_counter: u32 = 0, // unique counter for mangling local function names
|
||||||
import_flags: std.StringHashMap(bool), // tracks whether each function is imported
|
/// Declaration-name / import / visibility facts (architecture phase A1,
|
||||||
module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution)
|
/// `ProgramIndex`). Owns `import_flags`; borrows `module_scopes` /
|
||||||
import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, // module path → set of directly imported paths (used by param_impl_map visibility filter)
|
/// `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
|
current_source_file: ?[]const u8 = null, // source file of function currently being lowered
|
||||||
// Implicit Context parameter machinery. When the program imports
|
// Implicit Context parameter machinery. When the program imports
|
||||||
// `std.sx` (and therefore declares `Context :: struct {...}`), every
|
// `std.sx` (and therefore declares `Context :: struct {...}`), every
|
||||||
@@ -312,7 +315,7 @@ pub const Lowering = struct {
|
|||||||
.alloc = module.alloc,
|
.alloc = module.alloc,
|
||||||
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(module.alloc),
|
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(module.alloc),
|
||||||
.lowered_functions = std.StringHashMap(void).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),
|
.global_names = std.StringHashMap(GlobalInfo).init(module.alloc),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1479,14 +1482,14 @@ pub const Lowering = struct {
|
|||||||
switch (decl.data) {
|
switch (decl.data) {
|
||||||
.fn_decl => |fd| {
|
.fn_decl => |fd| {
|
||||||
self.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {};
|
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)
|
// Declare extern stub for all functions (bodies lowered lazily)
|
||||||
self.declareFunction(&fd, fd.name);
|
self.declareFunction(&fd, fd.name);
|
||||||
},
|
},
|
||||||
.const_decl => |cd| {
|
.const_decl => |cd| {
|
||||||
if (cd.value.data == .fn_decl) {
|
if (cd.value.data == .fn_decl) {
|
||||||
self.fn_ast_map.put(cd.name, &cd.value.data.fn_decl) catch {};
|
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);
|
self.declareFunction(&cd.value.data.fn_decl, cd.name);
|
||||||
} else if (cd.value.data == .struct_decl) {
|
} else if (cd.value.data == .struct_decl) {
|
||||||
self.registerStructDecl(&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
|
/// responsible for restricting the call to names that ARE known
|
||||||
/// top-level decls; otherwise every local variable would be policed.
|
/// top-level decls; otherwise every local variable would be policed.
|
||||||
fn isNameVisible(self: *Lowering, name: []const u8) bool {
|
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 source = self.current_source_file orelse return true;
|
||||||
const own_scope = scopes.get(source) orelse return true;
|
const own_scope = scopes.get(source) orelse return true;
|
||||||
if (own_scope.contains(name)) 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;
|
const direct = graph.get(source) orelse return true;
|
||||||
var it = direct.iterator();
|
var it = direct.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
@@ -11984,7 +11987,7 @@ pub const Lowering = struct {
|
|||||||
out.appendSlice(self.alloc, entries) catch {};
|
out.appendSlice(self.alloc, entries) catch {};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const graph = self.import_graph orelse {
|
const graph = self.program_index.import_graph orelse {
|
||||||
out.appendSlice(self.alloc, entries) catch {};
|
out.appendSlice(self.alloc, entries) catch {};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -14360,7 +14363,7 @@ pub const Lowering = struct {
|
|||||||
const method_fd = &method_node.data.fn_decl;
|
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;
|
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.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);
|
self.declareFunction(method_fd, qualified);
|
||||||
impl_methods.put(method_fd.name, {}) catch {};
|
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 synth_fd = self.synthesizeDefaultMethod(method, ib.target_type);
|
||||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ib.target_type, method.name }) catch continue;
|
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.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);
|
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;
|
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
|
if (self.fn_ast_map.contains(q)) continue; // first impl wins
|
||||||
self.fn_ast_map.put(q, mfd) catch {};
|
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);
|
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