ffi M1.2 A.4b.iii: class_addMethod wires IMPs to the Obj-C runtime
For each instance method on a sx-defined '#objc_class', the
class-pair init constructor now:
sel = sel_registerName("selector_string")
imp = @__<Cls>_<method>_imp (M1.2 A.4b.ii)
class_addMethod(cls, sel, imp, "<encoding>")
before objc_registerClassPair. The IMP trampoline (A.4b.ii)
already bridges C-ABI -> sx body. With registration in place,
'objc_msgSend(obj, sel_bump)' now routes to the trampoline,
which reads __sx_state ivar and forwards to '@<Cls>.<method>'.
To get selector + type-encoding strings out of lower.zig and
into emit_llvm, ObjcDefinedClassEntry gains a 'methods' slice:
pub const ObjcDefinedMethodEntry = struct {
sel: []const u8, // mangled selector (M1.2 A.1's deriveObjcSelector)
encoding: []const u8, // type encoding (M1.2 A.1's objcTypeEncodingFromSignature)
imp_name: []const u8, // C-callconv trampoline symbol
};
registerObjcDefinedClassMethods populates this when it declares
each method's body function; Module.setObjcDefinedClassMethods
attaches the slice to the cache entry by name. Static (class-
side) methods are skipped — A.4b only covers instance methods;
class-method hooks like '+layerClass' land in M2.1.
emit_llvm reads entry.methods and emits class_addMethod inside
the per-class init block, before objc_registerClassPair (the
runtime locks the method list at register time on some SDK
versions).
145-objc-class-method-dispatch.sx verifies end-to-end:
class_getMethodImplementation(SxFoo, sel_registerName("bump"))
returns non-null after main starts. Both niladic ('bump') and
single-arg ('add:') selectors checked.
Still gated (A.7): sx-side 'obj.bump()' calls. The dispatch
gate at lower.zig:4407 hasn't opened — A.5 (+alloc) and A.6
(-dealloc) need to land first so the integration test
ffi-objc-defined-class-01-instance.sx (full state round-trip)
can exercise the full lifecycle.
174 example tests pass (+1 from 145). zig build test green.
This commit is contained in:
@@ -9645,11 +9645,21 @@ pub const Lowering = struct {
|
||||
|
||||
/// For each bodied instance method on an sx-defined `#objc_class`,
|
||||
/// synthesize an `FnDecl` from the `ForeignMethodDecl`, register it
|
||||
/// in `fn_ast_map` under `<ClassName>.<methodName>`, and declare
|
||||
/// the IR function so callers can resolve the name. Bodyless
|
||||
/// declarations are skipped — they reference inherited / external
|
||||
/// methods, not sx-side bodies.
|
||||
/// in `fn_ast_map` under `<ClassName>.<methodName>`, declare the IR
|
||||
/// function, AND collect per-method registration data (selector
|
||||
/// mangling + type encoding + IMP symbol name) into the class's
|
||||
/// cache entry so emit_llvm can wire up `class_addMethod` calls
|
||||
/// (M1.2 A.4b.iii). Bodyless declarations are skipped — they
|
||||
/// reference inherited / external methods, not sx-side bodies.
|
||||
fn registerObjcDefinedClassMethods(self: *Lowering, fcd: *const ast.ForeignClassDecl) void {
|
||||
// Set current_foreign_class so `*Self` substitutions in
|
||||
// declareFunction's type resolution find the state struct.
|
||||
const saved = self.current_foreign_class;
|
||||
self.current_foreign_class = fcd;
|
||||
defer self.current_foreign_class = saved;
|
||||
|
||||
var method_infos = std.ArrayList(Module.ObjcDefinedMethodEntry).empty;
|
||||
|
||||
for (fcd.members) |m| {
|
||||
const method = switch (m) {
|
||||
.method => |md| md,
|
||||
@@ -9659,12 +9669,46 @@ pub const Lowering = struct {
|
||||
const fd = self.synthesizeFnDeclFromObjcMethod(method, body) orelse continue;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fcd.name, method.name }) catch continue;
|
||||
self.fn_ast_map.put(qualified, fd) catch {};
|
||||
// Set current_foreign_class while declaring so `*Self` in
|
||||
// the signature resolves to the state struct (M1.2 A.2b).
|
||||
const saved = self.current_foreign_class;
|
||||
self.current_foreign_class = fcd;
|
||||
defer self.current_foreign_class = saved;
|
||||
self.declareFunction(fd, qualified);
|
||||
|
||||
// Skip class methods (no `*Self` first param) for IMP wiring
|
||||
// — A.4b only covers instance methods. Class-side hooks
|
||||
// (`+layerClass` etc.) land in M2.1 via the metaclass.
|
||||
if (method.is_static) continue;
|
||||
|
||||
// Selector mangling — A.1's deriveObjcSelector handles
|
||||
// `#selector("...")` override + the default rule.
|
||||
const user_arg_count = if (method.params.len > 0) method.params.len - 1 else 0;
|
||||
const sel_info = self.deriveObjcSelector(method, user_arg_count);
|
||||
|
||||
// Type encoding for the IMP signature.
|
||||
// ABI: `(self: id, _cmd: SEL, ...user_args) -> ret`.
|
||||
// - return = method.return_type (or void)
|
||||
// - user_args = method.params[1..]
|
||||
// objcTypeEncodingFromSignature emits `<ret> @ : <args...>`
|
||||
// and the helper appends @ + : automatically.
|
||||
const ret_ty: TypeId = if (method.return_type) |rt| self.resolveType(rt) else .void;
|
||||
var arg_tys = std.ArrayList(TypeId).empty;
|
||||
defer arg_tys.deinit(self.alloc);
|
||||
if (method.params.len > 1) {
|
||||
for (method.params[1..]) |p_node| {
|
||||
arg_tys.append(self.alloc, self.resolveType(p_node)) catch unreachable;
|
||||
}
|
||||
}
|
||||
const encoding = self.objcTypeEncodingFromSignature(ret_ty, arg_tys.items, null) catch continue;
|
||||
|
||||
const imp_name = std.fmt.allocPrint(self.alloc, "__{s}_{s}_imp", .{ fcd.name, method.name }) catch continue;
|
||||
|
||||
method_infos.append(self.alloc, .{
|
||||
.sel = sel_info.sel,
|
||||
.encoding = encoding,
|
||||
.imp_name = imp_name,
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
if (method_infos.items.len > 0) {
|
||||
const methods_slice = method_infos.toOwnedSlice(self.alloc) catch return;
|
||||
self.module.setObjcDefinedClassMethods(fcd.name, methods_slice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user