ffi M1.2 A.5: synthesized +alloc IMP + ensureCRuntimeDecl helper
For every sx-defined #objc_class, emit a C-callconv +alloc IMP
that the Obj-C runtime calls when '[Cls alloc]' fires (from sx
code, UIKit instantiation, Info.plist principal class, etc.):
+alloc IMP (cls: Class, _cmd: SEL) -> id
instance = class_createInstance(cls, 0)
state = malloc(STATE_SIZE)
memset(state, 0, STATE_SIZE)
object_setIvar(instance, load(@__<Cls>_state_ivar), state)
return instance
STATE_SIZE = max(typeSizeBytes(state struct), 1) — always at
least one byte so the ivar is never null after +alloc returns.
The IMP is registered on the METACLASS (class methods live there
— every Class object's isa points to the metaclass) in emit_llvm's
class-pair init constructor:
metaclass = object_getClass(cls)
sel_alloc = sel_registerName("alloc")
class_addMethod(metaclass, sel_alloc, alloc_imp, "@@:")
That override wins over NSObject's default +alloc; runtime
instantiations get the __sx_state ivar bound automatically.
Per-instance allocator binding (the plan's full design — store
the Allocator value in the state struct so -dealloc frees through
the same one) is deferred. libc malloc/free is fine for v1; we'll
upgrade once Month 4's autoreleasepool + ARC ops shake out.
REFACTOR: collapsed five duplicate 'get<Name>Fid' helpers and
their cache fields (object_getIvar, object_setIvar,
class_createInstance, malloc, memset) into a single
'ensureCRuntimeDecl(name, params, ret) -> FuncId'. The helper
checks for an existing decl by name first (avoids the
'class_createInstance.1' duplicate-symbol crash when stdlib's
'#foreign' decl is already in the module). One helper instead
of one-per-function = ~150 lines deleted.
object_getIvar / object_setIvar added to stdlib std/objc.sx
so user code can use them too (146 exercises object_getIvar
to verify __sx_state was bound to a non-null state pointer
after +alloc).
146-objc-class-alloc-roundtrip.sx end-to-end against macOS:
'[SxFoo alloc]' returns non-null AND object_getIvar(instance,
__sx_state) returns the state ptr. Real Obj-C runtime, no
mocks.
175 example tests pass (+1). zig build test green.
This commit is contained in:
@@ -648,6 +648,40 @@ pub const LLVMEmitter = struct {
|
||||
var reg_args: [1]c.LLVMValueRef = .{cls_val};
|
||||
_ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, ®_args, 1, "");
|
||||
|
||||
// M1.2 A.5 — register the synthesized `+alloc` IMP on the
|
||||
// metaclass. Class methods live on the metaclass (every
|
||||
// Class object's `isa` points to the metaclass), so we
|
||||
// resolve it via `object_getClass(cls)` and `class_addMethod`
|
||||
// the IMP there. Encoding `@@:` = returns id, takes Class,
|
||||
// then SEL — Apple's standard `+alloc` shape. This override
|
||||
// wins over NSObject's default +alloc; runtime instantiations
|
||||
// (UIKit, Info.plist, NSCoder) go through our IMP and get the
|
||||
// __sx_state ivar bound.
|
||||
const alloc_imp_name = std.fmt.allocPrint(self.alloc, "__{s}_alloc_imp", .{class_name}) catch continue;
|
||||
defer self.alloc.free(alloc_imp_name);
|
||||
const alloc_imp_z = self.alloc.dupeZ(u8, alloc_imp_name) catch continue;
|
||||
defer self.alloc.free(alloc_imp_z);
|
||||
const alloc_imp_fn = c.LLVMGetNamedFunction(self.llvm_module, alloc_imp_z.ptr);
|
||||
if (alloc_imp_fn != null) {
|
||||
// metaclass = object_getClass(cls). (Distinct from
|
||||
// objc_getClass: the latter takes a NAME string and is
|
||||
// for class-object lookup. object_getClass takes an
|
||||
// instance pointer — a Class IS itself an instance of
|
||||
// its metaclass — and returns the isa.)
|
||||
const obj_get_class_fn, const obj_get_class_ty = self.lazyDeclareCRuntime("object_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0);
|
||||
var ogc_args: [1]c.LLVMValueRef = .{cls_val};
|
||||
const metaclass_val = c.LLVMBuildCall2(self.builder, obj_get_class_ty, obj_get_class_fn, &ogc_args, 1, "metacls");
|
||||
|
||||
const alloc_sel_global = self.emitPrivateCString("alloc", "OBJC_METH_VAR_NAME_");
|
||||
const alloc_enc_global = self.emitPrivateCString("@@:", "OBJC_METH_VAR_TYPE_");
|
||||
|
||||
var sel_args: [1]c.LLVMValueRef = .{alloc_sel_global};
|
||||
const sel_val = c.LLVMBuildCall2(self.builder, sel_reg_ty, sel_reg_fn, &sel_args, 1, "sel_alloc");
|
||||
|
||||
var add_args: [4]c.LLVMValueRef = .{ metaclass_val, sel_val, alloc_imp_fn, alloc_enc_global };
|
||||
_ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, "");
|
||||
}
|
||||
|
||||
// Cache the ivar handle in the per-class global so trampolines
|
||||
// can read the __sx_state ivar without re-looking-it-up. The
|
||||
// global is declared by lower.zig (M1.2 A.4b.i) and starts as
|
||||
|
||||
Reference in New Issue
Block a user