From 61a2593020e7979df12aeba1de4ea0fa4f081f5c Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 25 May 2026 21:37:36 +0300 Subject: [PATCH] ffi M1.2 A.0: objc_defined_class_cache + scan-pass registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/ir/lower.zig | 9 +++++++++ src/ir/module.zig | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 77e85b4..91cc93d 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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` diff --git a/src/ir/module.zig b/src/ir/module.zig index 05a33c9..c6f6e1c 100644 --- a/src/ir/module.zig +++ b/src/ir/module.zig @@ -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;