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:
agra
2026-05-25 22:58:20 +03:00
parent c0b338eaa4
commit 87572579b4
7 changed files with 168 additions and 11 deletions

View File

@@ -563,6 +563,10 @@ pub const LLVMEmitter = struct {
const alloc_pair_fn, const alloc_pair_ty = self.lazyDeclareCRuntime("objc_allocateClassPair", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty }, ptr_ty, 0);
// class_addIvar(cls: *void, name: *u8, size: u64, log2align: u8, type: *u8) -> bool.
const add_ivar_fn, const add_ivar_ty = self.lazyDeclareCRuntime("class_addIvar", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, i64_ty, i8_ty, ptr_ty }, i8_ty, 0);
// sel_registerName(name: *u8) -> *void.
const sel_reg_fn, const sel_reg_ty = self.lazyDeclareCRuntime("sel_registerName", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0);
// class_addMethod(cls: *void, sel: *void, imp: *void, types: *u8) -> bool.
const add_method_fn, const add_method_ty = self.lazyDeclareCRuntime("class_addMethod", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty, ptr_ty, ptr_ty }, i8_ty, 0);
// objc_registerClassPair(cls: *void) -> void.
const register_fn, const register_ty = self.lazyDeclareCRuntime("objc_registerClassPair", &[_]c.LLVMTypeRef{ptr_ty}, self.cached_void, 0);
// class_getInstanceVariable(cls: *void, name: *u8) -> *Ivar.
@@ -617,6 +621,29 @@ pub const LLVMEmitter = struct {
};
_ = c.LLVMBuildCall2(self.builder, add_ivar_ty, add_ivar_fn, &ivar_args, 5, "");
// class_addMethod(cls, sel_registerName(sel), imp, encoding)
// — register each instance method's IMP trampoline (M1.2 A.4b.iii).
// Must run BEFORE objc_registerClassPair; the runtime locks
// the method list at registration time on some SDK versions.
for (entry_kv.methods) |method| {
const sel_str_global = self.emitPrivateCString(method.sel, "OBJC_METH_VAR_NAME_");
const enc_str_global = self.emitPrivateCString(method.encoding, "OBJC_METH_VAR_TYPE_");
// sel = sel_registerName("selector")
var sel_args: [1]c.LLVMValueRef = .{sel_str_global};
const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel");
// imp = @__<ClassName>_<methodName>_imp
const imp_z = self.alloc.dupeZ(u8, method.imp_name) catch continue;
defer self.alloc.free(imp_z);
const imp_fn = c.LLVMGetNamedFunction(self.llvm_module, imp_z.ptr);
if (imp_fn == null) continue; // trampoline missing — skip
// class_addMethod(cls, sel, imp, encoding)
var add_args: [4]c.LLVMValueRef = .{ cls_val, sel_val, imp_fn, enc_str_global };
_ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, "");
}
// objc_registerClassPair(cls)
var reg_args: [1]c.LLVMValueRef = .{cls_val};
_ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, &reg_args, 1, "");