ffi M1.2 A.4b.i: __sx_state ivar registration
Class-pair init constructor now registers a single hidden ivar on each sx-defined class: class_addIvar(cls, "__sx_state", 8, 3, "^v") before objc_registerClassPair. After the class is registered, the constructor calls class_getInstanceVariable to fetch the runtime Ivar handle and stores it in a per-class global '__<ClassName>_state_ivar : *void'. Trampolines (A.4b.ii) will read this global to 'object_getIvar' the state struct pointer. lower.zig declares the per-class global at scan time (declareObjcDefinedStateIvarGlobal) so emit_llvm finds it by name when populating. Encoding '^v' = void* (a generic pointer — the runtime treats it as opaque storage). log2 alignment = 3 for 8-byte pointer alignment on 64-bit. 144-objc-class-ivar-registration.sx exercises the round-trip: after main starts, class_getInstanceVariable(SxFoo, "__sx_state") returns non-null. Runs against the real Obj-C runtime on macOS. 142's IR snapshot refreshed to include the new constructor body (class_addIvar + class_getInstanceVariable + ivar-global store). 173 example tests pass (+1 from 144). zig build test green.
This commit is contained in:
51
examples/144-objc-class-ivar-registration.sx
Normal file
51
examples/144-objc-class-ivar-registration.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
// M1.2 A.4b.i — '__sx_state' ivar registration on sx-defined
|
||||
// '#objc_class'.
|
||||
//
|
||||
// Class-pair init now:
|
||||
// 1. allocs the class pair
|
||||
// 2. registers a single '__sx_state : *void' ivar
|
||||
// 3. finalises the class
|
||||
// 4. stores the runtime Ivar handle in a per-class global
|
||||
// ('__<ClassName>_state_ivar') so IMP trampolines can later
|
||||
// 'object_getIvar' the state struct pointer.
|
||||
//
|
||||
// Round-trip below: after main starts, look up SxFoo, then ask
|
||||
// the runtime if SxFoo has an Ivar named '__sx_state'. Returns
|
||||
// non-null iff registration succeeded.
|
||||
//
|
||||
// IMP trampolines (A.4b.ii) and the '+alloc' / '-dealloc'
|
||||
// overrides (A.5 / A.6) come next.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/compiler.sx";
|
||||
#import "modules/std/objc.sx";
|
||||
|
||||
class_getInstanceVariable :: (cls: *void, name: [*]u8) -> *void #foreign objc;
|
||||
|
||||
SxFoo :: #objc_class("SxFoo") {
|
||||
counter: s32;
|
||||
|
||||
bump :: (self: *Self) {
|
||||
self.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
inline if OS == .macos {
|
||||
cls : Class = objc_getClass("SxFoo".ptr);
|
||||
if cls == null {
|
||||
print("FAIL: SxFoo not registered\n");
|
||||
return 1;
|
||||
}
|
||||
iv := class_getInstanceVariable(cls, "__sx_state".ptr);
|
||||
if iv == null {
|
||||
print("FAIL: __sx_state ivar missing\n");
|
||||
return 1;
|
||||
}
|
||||
print("ivar: __sx_state\n");
|
||||
}
|
||||
inline if OS != .macos {
|
||||
print("ivar: __sx_state\n");
|
||||
}
|
||||
0;
|
||||
}
|
||||
@@ -542,26 +542,31 @@ pub const LLVMEmitter = struct {
|
||||
/// For each entry in `objc_defined_class_cache`:
|
||||
/// super_cls = objc_getClass("<ParentName>") // default NSObject
|
||||
/// cls = objc_allocateClassPair(super_cls, "<ClassName>", 0)
|
||||
/// class_addIvar(cls, "__sx_state", 8, 3, "^v") // M1.2 A.4b.i
|
||||
/// objc_registerClassPair(cls)
|
||||
/// g_<ClassName>_state_ivar = class_getInstanceVariable(cls, "__sx_state")
|
||||
///
|
||||
/// Method IMPs, the `__sx_state` ivar, and the `+alloc` /
|
||||
/// `-dealloc` overrides come in A.4b / A.5 / A.6 — this slice
|
||||
/// just makes the class exist in the runtime so `objc_getClass`
|
||||
/// finds it.
|
||||
/// Method IMPs (`class_addMethod`) and the `+alloc` / `-dealloc`
|
||||
/// overrides come in A.4b.ii / A.5 / A.6.
|
||||
fn emitObjcDefinedClassInit(self: *LLVMEmitter) void {
|
||||
if (self.ir_mod.objc_defined_class_cache.items.len == 0) return;
|
||||
|
||||
const ptr_ty = self.cached_ptr;
|
||||
const i32_ty = self.cached_i32;
|
||||
const i64_ty = self.cached_i64;
|
||||
const i8_ty = c.LLVMInt8TypeInContext(self.context);
|
||||
|
||||
// Lazy-declare the Obj-C runtime APIs the constructor calls.
|
||||
// objc_getClass(name: *u8) -> *void.
|
||||
const get_class_fn, const get_class_ty = self.lazyDeclareCRuntime("objc_getClass", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0);
|
||||
// objc_allocateClassPair(super: *void, name: *u8, extra: usize) -> *void.
|
||||
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);
|
||||
// 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.
|
||||
const get_iv_fn, const get_iv_ty = self.lazyDeclareCRuntime("class_getInstanceVariable", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, ptr_ty, 0);
|
||||
|
||||
// Constructor: void __sx_objc_defined_class_init().
|
||||
var no_params: [0]c.LLVMTypeRef = .{};
|
||||
@@ -571,6 +576,10 @@ pub const LLVMEmitter = struct {
|
||||
const entry = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry");
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, entry);
|
||||
|
||||
// Reusable C-string globals for ivar metadata (same across classes).
|
||||
const sx_state_name_global = self.emitPrivateCString("__sx_state", "OBJC_IVAR_NAME_");
|
||||
const sx_state_enc_global = self.emitPrivateCString("^v", "OBJC_IVAR_TYPE_");
|
||||
|
||||
for (self.ir_mod.objc_defined_class_cache.items) |entry_kv| {
|
||||
const fcd = entry_kv.decl;
|
||||
const class_name = fcd.name;
|
||||
@@ -595,9 +604,37 @@ pub const LLVMEmitter = struct {
|
||||
var alloc_args: [3]c.LLVMValueRef = .{ super_val, class_str_global, c.LLVMConstInt(i64_ty, 0, 0) };
|
||||
const cls_val = c.LLVMBuildCall2(self.builder, alloc_pair_ty, alloc_pair_fn, &alloc_args, 3, "cls");
|
||||
|
||||
// class_addIvar(cls, "__sx_state", 8, 3, "^v")
|
||||
// size = 8 (pointer) — sizeof(*void) on 64-bit
|
||||
// log2align = 3 — alignof(*void) = 8 = 2^3
|
||||
// type = "^v" (encoded *void)
|
||||
var ivar_args: [5]c.LLVMValueRef = .{
|
||||
cls_val,
|
||||
sx_state_name_global,
|
||||
c.LLVMConstInt(i64_ty, 8, 0),
|
||||
c.LLVMConstInt(i8_ty, 3, 0),
|
||||
sx_state_enc_global,
|
||||
};
|
||||
_ = c.LLVMBuildCall2(self.builder, add_ivar_ty, add_ivar_fn, &ivar_args, 5, "");
|
||||
|
||||
// objc_registerClassPair(cls)
|
||||
var reg_args: [1]c.LLVMValueRef = .{cls_val};
|
||||
_ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, ®_args, 1, "");
|
||||
|
||||
// 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
|
||||
// null; the constructor fills it in here.
|
||||
const ivar_global_name = std.fmt.allocPrint(self.alloc, "__{s}_state_ivar", .{class_name}) catch continue;
|
||||
defer self.alloc.free(ivar_global_name);
|
||||
const ivar_global_z = self.alloc.dupeZ(u8, ivar_global_name) catch continue;
|
||||
defer self.alloc.free(ivar_global_z);
|
||||
const ivar_global = c.LLVMGetNamedGlobal(self.llvm_module, ivar_global_z.ptr);
|
||||
if (ivar_global != null) {
|
||||
var iv_args: [2]c.LLVMValueRef = .{ cls_val, sx_state_name_global };
|
||||
const iv_val = c.LLVMBuildCall2(self.builder, get_iv_ty, get_iv_fn, &iv_args, 2, "iv");
|
||||
_ = c.LLVMBuildStore(self.builder, iv_val, ivar_global);
|
||||
}
|
||||
}
|
||||
_ = c.LLVMBuildRetVoid(self.builder);
|
||||
|
||||
|
||||
@@ -9617,11 +9617,31 @@ pub const Lowering = struct {
|
||||
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
|
||||
if (self.module.lookupObjcDefinedClass(fcd.name) == null) {
|
||||
self.module.appendObjcDefinedClass(fcd.name, fcd);
|
||||
// M1.2 A.4b.i: per-class ivar handle global. The class-pair
|
||||
// init constructor (emit_llvm) populates it via
|
||||
// class_getInstanceVariable after the class is registered;
|
||||
// IMP trampolines read it to find the __sx_state ivar.
|
||||
self.declareObjcDefinedStateIvarGlobal(fcd.name);
|
||||
}
|
||||
self.registerObjcDefinedClassMethods(fcd);
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare a per-class global `__<ClassName>_state_ivar : *void = null`.
|
||||
/// emit_llvm's `emitObjcDefinedClassInit` constructor fills it in via
|
||||
/// `class_getInstanceVariable(cls, "__sx_state")` once per module load.
|
||||
fn declareObjcDefinedStateIvarGlobal(self: *Lowering, class_name: []const u8) void {
|
||||
const gname = std.fmt.allocPrint(self.alloc, "__{s}_state_ivar", .{class_name}) catch return;
|
||||
const name_id = self.module.types.internString(gname);
|
||||
_ = self.module.addGlobal(.{
|
||||
.name = name_id,
|
||||
.ty = self.module.types.ptrTo(.void),
|
||||
.init_val = .null_val,
|
||||
.is_extern = false,
|
||||
.is_const = false,
|
||||
});
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@OS = internal global i64 0
|
||||
@ARCH = internal global i64 0
|
||||
@POINTER_SIZE = internal global i64 8
|
||||
@__SxFoo_state_ivar = internal global ptr null
|
||||
@__sx_default_context = internal global { { ptr, ptr, ptr }, ptr } { { ptr, ptr, ptr } { ptr null, ptr @__thunk_CAllocator_Allocator_alloc, ptr @__thunk_CAllocator_Allocator_dealloc }, ptr null }
|
||||
@str = private unnamed_addr constant [2 x i8] c"0\00", align 1
|
||||
@str.1 = private unnamed_addr constant [15 x i8] c"result := \22\22; \00", align 1
|
||||
@@ -22,6 +23,8 @@
|
||||
@str.16 = private unnamed_addr constant [10 x i8] c"compiled\0A\00", align 1
|
||||
@str.17 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1
|
||||
@str.18 = private unnamed_addr constant [10 x i8] c"compiled\0A\00", align 1
|
||||
@OBJC_IVAR_NAME_ = private unnamed_addr constant [11 x i8] c"__sx_state\00"
|
||||
@OBJC_IVAR_TYPE_ = private unnamed_addr constant [3 x i8] c"^v\00"
|
||||
@OBJC_CLASS_NAME_ = private unnamed_addr constant [9 x i8] c"NSObject\00"
|
||||
@OBJC_CLASS_NAME_.19 = private unnamed_addr constant [6 x i8] c"SxFoo\00"
|
||||
@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__sx_objc_defined_class_init, ptr null }]
|
||||
@@ -787,12 +790,19 @@ declare ptr @objc_getClass(ptr)
|
||||
|
||||
declare ptr @objc_allocateClassPair(ptr, ptr, i64)
|
||||
|
||||
declare i8 @class_addIvar(ptr, ptr, i64, i8, ptr)
|
||||
|
||||
declare void @objc_registerClassPair(ptr)
|
||||
|
||||
declare ptr @class_getInstanceVariable(ptr, ptr)
|
||||
|
||||
define internal void @__sx_objc_defined_class_init() {
|
||||
entry:
|
||||
%super_cls = call ptr @objc_getClass(ptr @OBJC_CLASS_NAME_)
|
||||
%cls = call ptr @objc_allocateClassPair(ptr %super_cls, ptr @OBJC_CLASS_NAME_.19, i64 0)
|
||||
%0 = call i8 @class_addIvar(ptr %cls, ptr @OBJC_IVAR_NAME_, i64 8, i8 3, ptr @OBJC_IVAR_TYPE_)
|
||||
call void @objc_registerClassPair(ptr %cls)
|
||||
%iv = call ptr @class_getInstanceVariable(ptr %cls, ptr @OBJC_IVAR_NAME_)
|
||||
store ptr %iv, ptr @__SxFoo_state_ivar, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
1
tests/expected/144-objc-class-ivar-registration.exit
Normal file
1
tests/expected/144-objc-class-ivar-registration.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
tests/expected/144-objc-class-ivar-registration.txt
Normal file
1
tests/expected/144-objc-class-ivar-registration.txt
Normal file
@@ -0,0 +1 @@
|
||||
ivar: __sx_state
|
||||
Reference in New Issue
Block a user