ffi M1.2 A.0: objc_defined_class_cache + scan-pass registration

Adds an insertion-ordered cache on Module for sx-defined Obj-C
classes — every '#objc_class("Cls") { ... }' declaration WITHOUT
'#foreign'. registerForeignClassDecl appends the entry alongside
its existing foreign_class_map insert; lookup helper available
via Module.lookupObjcDefinedClass.

  ObjcDefinedClassEntry { name, *const ast.ForeignClassDecl }

The pointer back into the AST lets later passes (M1.2 A.1+) walk
'members' for fields / methods / '#extends' / '#implements'
without duplicating that data on the entry. Insertion order
matters because class-pair init constructors (A.4) must register
parent classes before children — 'objc_allocateClassPair(super,
...)' resolves super by lookup.

Infrastructure only — no observable behavior change. The cache
is populated but not yet read; A.1+ start pulling from it. 170
example tests + zig build test green.
This commit is contained in:
agra
2026-05-25 21:37:36 +03:00
parent d9dbdad3f5
commit 61a2593020
2 changed files with 37 additions and 0 deletions

View File

@@ -9413,8 +9413,17 @@ pub const Lowering = struct {
/// type (e.g. `*Activity`) is resolved via the existing struct
/// fallback in `type_bridge.resolveTypeName` (which interns unknown
/// named types as 0-field structs).
///
/// sx-defined Obj-C classes (no `#foreign`, runtime == .objc_class)
/// also land in `module.objc_defined_class_cache` in declaration
/// order — that cache drives M1.2 class-synthesis emission (A.4+).
fn registerForeignClassDecl(self: *Lowering, fcd: *const ast.ForeignClassDecl) void {
self.foreign_class_map.put(fcd.name, fcd) catch {};
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
if (self.module.lookupObjcDefinedClass(fcd.name) == null) {
self.module.appendObjcDefinedClass(fcd.name, fcd);
}
}
}
/// Lazily declare the `sx_jni_env_tl_get` / `sx_jni_env_tl_set`

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const types = @import("types.zig");
const inst = @import("inst.zig");
const ast = @import("../ast.zig");
const TypeId = types.TypeId;
const TypeInfo = types.TypeInfo;
@@ -41,6 +42,15 @@ pub const Module = struct {
/// `Cls.static_method(...)` against an `#objc_class` alias resolves
/// the class object through this cache once per module.
objc_class_cache: std.ArrayList(ObjcClassEntry),
/// sx-defined Obj-C classes — every `Cls :: #objc_class("Cls") { ... }`
/// declaration WITHOUT `#foreign`. Insertion-ordered so the
/// class-registration constructors (M1.2 A.4) emit in source order
/// — parent classes register before children, which matters because
/// `objc_allocateClassPair(super, ...)` resolves `super` by lookup.
/// Each entry holds a pointer back into the AST so later passes
/// (trampoline emission, +alloc/-dealloc synthesis) can re-walk
/// `members` for fields / methods / `#extends` / `#implements`.
objc_defined_class_cache: std.ArrayList(ObjcDefinedClassEntry),
alloc: Allocator,
/// True when this module's program imports `std.sx` (and therefore
/// has the `Context` type). Set by lowering's Pass 0 pre-scan. Read
@@ -50,6 +60,10 @@ pub const Module = struct {
pub const ObjcSelectorEntry = struct { sel: []const u8, slot: GlobalId };
pub const ObjcClassEntry = struct { name: []const u8, slot: GlobalId };
/// Pointer back to the AST node lets later passes re-walk `members`
/// for fields / methods / `#extends` / `#implements` without
/// duplicating that data here.
pub const ObjcDefinedClassEntry = struct { name: []const u8, decl: *const ast.ForeignClassDecl };
pub fn init(alloc: Allocator) Module {
return .{
@@ -59,6 +73,7 @@ pub const Module = struct {
.impl_table = ImplTable.init(alloc),
.objc_selector_cache = std.ArrayList(ObjcSelectorEntry).empty,
.objc_class_cache = std.ArrayList(ObjcClassEntry).empty,
.objc_defined_class_cache = std.ArrayList(ObjcDefinedClassEntry).empty,
.alloc = alloc,
};
}
@@ -72,6 +87,7 @@ pub const Module = struct {
self.impl_table.deinit();
self.objc_selector_cache.deinit(self.alloc);
self.objc_class_cache.deinit(self.alloc);
self.objc_defined_class_cache.deinit(self.alloc);
self.types.deinit();
}
@@ -101,6 +117,18 @@ pub const Module = struct {
self.objc_class_cache.append(self.alloc, .{ .name = name, .slot = slot }) catch unreachable;
}
/// Linear scan over sx-defined Obj-C classes.
pub fn lookupObjcDefinedClass(self: *const Module, name: []const u8) ?*const ast.ForeignClassDecl {
for (self.objc_defined_class_cache.items) |entry| {
if (std.mem.eql(u8, entry.name, name)) return entry.decl;
}
return null;
}
pub fn appendObjcDefinedClass(self: *Module, name: []const u8, decl: *const ast.ForeignClassDecl) void {
self.objc_defined_class_cache.append(self.alloc, .{ .name = name, .decl = decl }) catch unreachable;
}
pub fn addFunction(self: *Module, func: Function) FuncId {
const id = FuncId.fromIndex(@intCast(self.functions.items.len));
self.functions.append(self.alloc, func) catch unreachable;