ffi 3.1: Cls.static_method(args) lowers to objc_msg_send on the class object
Implementation half of the Phase 3.1 cadence step. `lowerForeignStaticCall` for `#objc_class` / `#objc_protocol` runtimes no longer bails; it routes through a new `lowerObjcStaticCall` helper that loads the class object from a module-scoped cached slot (populated once per module via `objc_getClass`) and dispatches `objc_msg_send` with the same selector-mangling as Phase 3.0's instance dispatch. Three pieces: 1. `Module.objc_class_cache` — parallel to `objc_selector_cache`, insertion-ordered list of (class_name, slot_GlobalId) so the constructor that calls `objc_getClass` per slot at module load is deterministic. `lookupObjcClass` / `appendObjcClass` accessors. 2. `internObjcClassObject` in lower.zig — get-or-create a `OBJC_CLASSLIST_REFERENCES_<Cls>` global pointer; matches clang's naming convention. `lowerObjcStaticCall` reuses `deriveObjcSelector` from 3.0 for the selector, loads the class slot, and emits `objc_msg_send(class_obj, sel, args)`. 3. `emitObjcClassInit` in emit_llvm.zig — companion to `emitObjcSelectorInit`. Walks `objc_class_cache`, synthesizes a constructor `__sx_objc_class_init` that calls `objc_getClass(name)` per slot, registers in `@llvm.global_ctors` for AOT (extending the existing array if the selector init already created it), and injects a direct call into main's prelude after any prior init calls so the ORC JIT path runs it too. Surface form is `.` (`NSObject.class()`) matching JNI's `Alias.new(...)` convention rather than the plan's notional `::` — avoids extending the parser for a new postfix operator with no other use case. Test `examples/ffi-objc-dsl-05-static.sx` exercises NSObject's `+class` and `+description` class methods via the new syntax, asserts both return non-null. NSObject is always available at module-load, unlike runtime-created test classes that wouldn't exist yet when the class-init constructor runs. 164/164 tests; chess builds + runs clean on all three platforms.
This commit is contained in:
@@ -33,6 +33,14 @@ pub const Module = struct {
|
||||
/// hashtable rehash). `#objc_call` lowering uses
|
||||
/// `lookupObjcSelector` / `appendObjcSelector` to read/write it.
|
||||
objc_selector_cache: std.ArrayList(ObjcSelectorEntry),
|
||||
/// Interned Obj-C class objects. Parallel structure to
|
||||
/// `objc_selector_cache` — kept as an insertion-ordered list of
|
||||
/// (class_name, slot_GlobalId) so the constructor that calls
|
||||
/// `objc_getClass` per slot at module load is deterministic.
|
||||
/// Used by static method dispatch (Phase 3.1) — every
|
||||
/// `Cls.static_method(...)` against an `#objc_class` alias resolves
|
||||
/// the class object through this cache once per module.
|
||||
objc_class_cache: std.ArrayList(ObjcClassEntry),
|
||||
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
|
||||
@@ -41,6 +49,7 @@ pub const Module = struct {
|
||||
has_implicit_ctx: bool = false,
|
||||
|
||||
pub const ObjcSelectorEntry = struct { sel: []const u8, slot: GlobalId };
|
||||
pub const ObjcClassEntry = struct { name: []const u8, slot: GlobalId };
|
||||
|
||||
pub fn init(alloc: Allocator) Module {
|
||||
return .{
|
||||
@@ -49,6 +58,7 @@ pub const Module = struct {
|
||||
.globals = std.ArrayList(Global).empty,
|
||||
.impl_table = ImplTable.init(alloc),
|
||||
.objc_selector_cache = std.ArrayList(ObjcSelectorEntry).empty,
|
||||
.objc_class_cache = std.ArrayList(ObjcClassEntry).empty,
|
||||
.alloc = alloc,
|
||||
};
|
||||
}
|
||||
@@ -61,6 +71,7 @@ pub const Module = struct {
|
||||
self.globals.deinit(self.alloc);
|
||||
self.impl_table.deinit();
|
||||
self.objc_selector_cache.deinit(self.alloc);
|
||||
self.objc_class_cache.deinit(self.alloc);
|
||||
self.types.deinit();
|
||||
}
|
||||
|
||||
@@ -78,6 +89,18 @@ pub const Module = struct {
|
||||
self.objc_selector_cache.append(self.alloc, .{ .sel = sel, .slot = slot }) catch unreachable;
|
||||
}
|
||||
|
||||
/// Linear scan — same rationale as `lookupObjcSelector`.
|
||||
pub fn lookupObjcClass(self: *const Module, name: []const u8) ?GlobalId {
|
||||
for (self.objc_class_cache.items) |entry| {
|
||||
if (std.mem.eql(u8, entry.name, name)) return entry.slot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn appendObjcClass(self: *Module, name: []const u8, slot: GlobalId) void {
|
||||
self.objc_class_cache.append(self.alloc, .{ .name = name, .slot = slot }) 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;
|
||||
|
||||
Reference in New Issue
Block a user