refactor(ir): move declaration maps into ProgramIndex (A1.1b)

Architecture phase A1.1b — mechanical storage relocation. Move the 9
declaration-fact maps out of the Lowering state bag into ProgramIndex:

  high-fanout:   fn_ast_map, foreign_class_map, global_names, type_alias_map
  medium-fanout: struct_template_map, protocol_decl_map, protocol_ast_map,
                 module_const_map, ufcs_alias_map

168 self.<map> sites in lower.zig repointed to self.program_index.<map>;
external readers repointed too (core.zig foreign_class_map iteration;
lower.test.zig fn_ast_map / foreign_class_map). No duplicate storage, no
fallback path; zig build enforces no missed reference.

The four maps whose value types were Lowering-private pull those types into
program_index.zig as pub (GlobalInfo, StructTemplate + TemplateParam,
ProtocolDeclInfo + ProtocolMethodInfo, ModuleConstInfo); lower.zig aliases
them at file scope so call sites are unchanged.

Behavior is preserved exactly:
- per-map allocator unchanged — import_flags/fn_ast_map/global_names use the
  lowering allocator (ProgramIndex.init), the other 7 keep their page_allocator
  inline defaults;
- ProgramIndex.deinit frees only the 10 owned maps, never the borrowed
  module_scopes / import_graph;
- TypeTable.aliases still borrows &self.program_index.type_alias_map, loaned at
  lowerRoot with the same late-binding lifetime.

Extends program_index.test.zig with declaration-map round-trips (fn AST, type
alias, global, module const, foreign class, protocol decl/AST, struct template,
ufcs alias).

Registration logic (registerStructDecl / registerProtocolDecl /
registerForeignClassDecl, ...) stays in Lowering, writing through the index.

Gate green: zig build, zig build test, bash tests/run_examples.sh
(350 passed, 0 failed). lower.zig 19433 -> 19393 lines.
This commit is contained in:
agra
2026-06-02 12:30:11 +03:00
parent 90520eefeb
commit fb262e9e59
5 changed files with 337 additions and 244 deletions

View File

@@ -328,7 +328,7 @@ pub const Compilation = struct {
return module;
}
/// Walk `lowering.foreign_class_map` and render Java sources for every
/// Walk `lowering.program_index.foreign_class_map` and render Java sources for every
/// `#jni_main #jni_class("...")` declaration. Renders happen here so the
/// AST + class-registry snapshot stay confined to the lowering pass; the
/// downstream APK pipeline only needs `{foreign_path, java_source}` pairs.
@@ -342,7 +342,7 @@ pub const Compilation = struct {
// and `#extends Alias` resolution.
var registry = std.StringHashMap([]const u8).init(self.allocator);
defer registry.deinit();
var it_reg = lowering.foreign_class_map.iterator();
var it_reg = lowering.program_index.foreign_class_map.iterator();
while (it_reg.next()) |entry| {
try registry.put(entry.key_ptr.*, entry.value_ptr.*.foreign_path);
}
@@ -353,7 +353,7 @@ pub const Compilation = struct {
// .so loading via another class.
const lib_name = libNameFromOutputPath(self.target_config.output_path);
var it = lowering.foreign_class_map.iterator();
var it = lowering.program_index.foreign_class_map.iterator();
while (it.next()) |entry| {
const fcd = entry.value_ptr.*;
if (!fcd.is_main) continue;

View File

@@ -479,7 +479,7 @@ test "lower: objcTypeEncodingFromSignature emits @ for Obj-C class pointers" {
.is_foreign = true,
.is_main = false,
};
try lowering.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
// Return *NSString, no args: "@@:"
const e1 = try lowering.objcTypeEncodingFromSignature(ns_ptr, &.{}, null);
@@ -510,7 +510,7 @@ test "lower: objcTypeEncodingFromSignature unwraps optional to wire type" {
.is_foreign = true,
.is_main = false,
};
try lowering.foreign_class_map.put("NSString", &ns_fcd);
try lowering.program_index.foreign_class_map.put("NSString", &ns_fcd);
// `?s64 -> ?*NSString` collapses to `q -> @` at the Obj-C boundary.
const opt_s64 = module.types.optionalOf(.s64);
@@ -750,8 +750,8 @@ test "E1.4b converge inferred error sets: empty -> warning, raising -> converged
r_body.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = r_stmts } } };
const raiser_fd = ast.FnDecl{ .name = "raiser", .params = &.{}, .return_type = r_rt, .body = r_body };
lowering.fn_ast_map.put("stub", &stub_fd) catch unreachable;
lowering.fn_ast_map.put("raiser", &raiser_fd) catch unreachable;
lowering.program_index.fn_ast_map.put("stub", &stub_fd) catch unreachable;
lowering.program_index.fn_ast_map.put("raiser", &raiser_fd) catch unreachable;
lowering.convergeInferredErrorSets();

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
const std = @import("std");
const ProgramIndex = @import("program_index.zig").ProgramIndex;
const pi = @import("program_index.zig");
const ProgramIndex = pi.ProgramIndex;
const ast = @import("../ast.zig");
const types = @import("types.zig");
const inst = @import("inst.zig");
test "ProgramIndex.init starts empty with unset borrowed views" {
var idx = ProgramIndex.init(std.testing.allocator);
@@ -38,3 +42,57 @@ test "ProgramIndex borrows module_scopes / import_graph without owning them" {
try std.testing.expect(idx.import_graph.? == &graph);
try std.testing.expectEqual(@as(u32, 0), idx.module_scopes.?.count());
}
test "ProgramIndex declaration maps round-trip (A1.1b)" {
var idx = ProgramIndex.init(std.testing.allocator);
defer idx.deinit();
// Minimal AST node reused wherever a *Node is required.
var blk = ast.Node{ .span = .{ .start = 0, .end = 0 }, .data = .{ .block = .{ .stmts = &.{} } } };
// fn_ast_map: function name → AST decl.
const fd = ast.FnDecl{ .name = "main", .params = &.{}, .return_type = null, .body = &blk };
try idx.fn_ast_map.put("main", &fd);
try std.testing.expect(idx.fn_ast_map.get("main").? == &fd);
// type_alias_map: alias name → target TypeId.
try idx.type_alias_map.put("ShaderHandle", .s64);
try std.testing.expectEqual(@as(?types.TypeId, .s64), idx.type_alias_map.get("ShaderHandle"));
// global_names: #run global name → GlobalInfo.
try idx.global_names.put("g", .{ .id = inst.GlobalId.fromIndex(0), .ty = .s64 });
try std.testing.expect(idx.global_names.get("g").?.id == inst.GlobalId.fromIndex(0));
// module_const_map: const name → ModuleConstInfo.
try idx.module_const_map.put("AF_INET", .{ .value = &blk, .ty = .s32 });
try std.testing.expect(idx.module_const_map.get("AF_INET").?.value == &blk);
// foreign_class_map: sx alias → ForeignClassDecl.
const fcd = ast.ForeignClassDecl{
.name = "NSString",
.foreign_path = "NSString",
.runtime = .objc_class,
.members = &.{},
.is_foreign = true,
.is_main = false,
};
try idx.foreign_class_map.put("NSString", &fcd);
try std.testing.expect(idx.foreign_class_map.get("NSString").? == &fcd);
// protocol_decl_map: protocol name → ProtocolDeclInfo.
try idx.protocol_decl_map.put("Show", .{ .name = "Show", .is_inline = false, .methods = &.{} });
try std.testing.expectEqualStrings("Show", idx.protocol_decl_map.get("Show").?.name);
// protocol_ast_map: protocol name → AST decl.
const pd = ast.ProtocolDecl{ .name = "Show", .methods = &.{} };
try idx.protocol_ast_map.put("Show", &pd);
try std.testing.expect(idx.protocol_ast_map.get("Show").? == &pd);
// struct_template_map: generic struct name → template.
try idx.struct_template_map.put("List", .{ .name = "List", .type_params = &.{}, .field_names = &.{}, .field_type_nodes = &.{} });
try std.testing.expectEqualStrings("List", idx.struct_template_map.get("List").?.name);
// ufcs_alias_map: alias name → target function name.
try idx.ufcs_alias_map.put("len", "list_len");
try std.testing.expectEqualStrings("list_len", idx.ufcs_alias_map.get("len").?);
}

View File

@@ -1,36 +1,111 @@
const std = @import("std");
const ast = @import("../ast.zig");
const types = @import("types.zig");
const inst = @import("inst.zig");
/// 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`.
const Node = ast.Node;
const TypeId = types.TypeId;
/// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports)
pub const StructTemplate = struct {
name: []const u8,
type_params: []const TemplateParam,
field_names: []const []const u8,
field_type_nodes: []const *const Node, // raw AST pointers — must be copied from heap nodes
};
pub const TemplateParam = struct {
name: []const u8,
is_type_param: bool, // true for $T: Type, false for $N: u32
is_variadic: bool = false, // `..$Ts: []Type` — binds remaining type args as a pack
};
pub const ProtocolMethodInfo = struct {
name: []const u8,
param_types: []const TypeId, // excluding self
ret_type: TypeId,
// True when the AST return type was `Self` (encoded here as *void).
// Lets the dispatcher distinguish Self-disguised-as-*void (auto-unbox
// on the caller side) from a literal `-> *void` (return as-is).
ret_is_self: bool = false,
};
pub const ProtocolDeclInfo = struct {
name: []const u8,
is_inline: bool,
methods: []const ProtocolMethodInfo,
};
pub const ModuleConstInfo = struct {
value: *const Node,
ty: TypeId,
};
pub const GlobalInfo = struct { id: inst.GlobalId, ty: TypeId };
/// Single lowering access point for declaration-name / import / visibility
/// facts. The architecture stream (`current/PLAN-ARCH.md`, phase A1) extracts
/// these out of the `Lowering` state bag incrementally. `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`.
///
/// `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.
/// OWNS the declaration maps below. BORROWS `module_scopes` / `import_graph`
/// (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
/// declaration scan / registration code in `Lowering`; read everywhere else.
pub const ProgramIndex = struct {
// ── Import / visibility ──
/// 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.
/// Per-module visible names, keyed by source file. Borrowed view.
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.
/// Module path → set of directly imported paths (param_impl visibility
/// filter). Borrowed view.
import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null,
// ── Declaration maps ──
/// Function name → AST decl.
fn_ast_map: std.StringHashMap(*const ast.FnDecl),
/// 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),
/// `#run` global name → GlobalId.
global_names: std.StringHashMap(GlobalInfo),
/// Type alias name → target TypeId. Loaned to `TypeTable.aliases`.
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator),
/// Generic struct name → template.
struct_template_map: std.StringHashMap(StructTemplate) = std.StringHashMap(StructTemplate).init(std.heap.page_allocator),
/// Protocol name → protocol info.
protocol_decl_map: std.StringHashMap(ProtocolDeclInfo) = std.StringHashMap(ProtocolDeclInfo).init(std.heap.page_allocator),
/// Protocol name → AST node.
protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator),
/// Module-level value constants (e.g. AF_INET :s32: 2).
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator),
/// UFCS alias name → target function name.
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator),
pub fn init(alloc: std.mem.Allocator) ProgramIndex {
return .{ .import_flags = std.StringHashMap(bool).init(alloc) };
return .{
.import_flags = std.StringHashMap(bool).init(alloc),
.fn_ast_map = std.StringHashMap(*const ast.FnDecl).init(alloc),
.global_names = std.StringHashMap(GlobalInfo).init(alloc),
};
}
pub fn deinit(self: *ProgramIndex) void {
// Owned maps only — module_scopes / import_graph are borrowed.
self.import_flags.deinit();
self.fn_ast_map.deinit();
self.foreign_class_map.deinit();
self.global_names.deinit();
self.type_alias_map.deinit();
self.struct_template_map.deinit();
self.protocol_decl_map.deinit();
self.protocol_ast_map.deinit();
self.module_const_map.deinit();
self.ufcs_alias_map.deinit();
}
};