ffi M3.1 + M1.2 A.3 refactor: self=Obj-C id, self.field via ivar; SxAppDelegate migrated
Two coupled changes that unblock the uikit_register_classes
migration:
1) M1.2 A.3 — body's 'self' is the Obj-C id (opaque), NOT the
state struct. Matches Apple's ObjC semantics where 'self' IS
the object. Cocoa idiom 'xx self → id' works at runtime calls
(addObserver:, etc.); previously the trampoline replaced
'self' with the state-struct pointer, breaking any runtime
call that expected an id.
'*Self' substitution in resolveTypeWithBindings now points at
foreignClassStructType(fcd) — the opaque class stub — instead
of objcDefinedStateStructType(fcd).
'self.field' access on a sx-defined class instance field is
rewritten by lowerFieldAccess to go through the __sx_state
ivar:
state = object_getIvar(self, load(__<Cls>_state_ivar))
val = struct_gep(state, field_idx) → load
Both read (lowerFieldAccess) and write (lowerAssignment) take
this path. Compound ops (+=, -=, etc.) are supported via
storeOrCompound. The lookup is filtered: skip property fields
(those still go through the M2.2 msgSend getter/setter
dispatch) and foreign classes (no state).
New helpers in lower.zig:
- lookupObjcDefinedStateFieldOnPointer — match check.
- lowerObjcDefinedStateForObj — emit the object_getIvar +
ivar-global-load idiom (shared between read + write paths).
- lowerObjcDefinedStateFieldRead — the load path.
Also moved the @llvm.global_ctors registration out of the
sx-defined class-pair init constructor — global_ctors fires
DURING dyld's framework load, before UIKit registers its Obj-C
classes. objc_getClass("UIResponder") returned null, super
was null, objc_registerClassPair crashed. main's entry block
is post-framework-load but pre-user-code — exactly the right
window. New helper injectCtorIntoMain.
2) M3.1 — SxAppDelegate migrated to declarative #objc_class.
uikit_register_classes' hand-rolled objc_allocateClassPair +
class_addMethod for SxAppDelegate is gone; the compiler
synthesises the class at module init. The method bodies
forward to the existing legacy IMP free functions
(uikit_did_finish_launching, uikit_keyboard_will_change_frame)
so we don't have to inline 70+ lines of keyboard-frame logic
right now.
Also adds UIResponder foreign-class declaration and chains
UIView / UITextField to it via #extends UIResponder so the
methods that previously lived on UITextField directly
(becomeFirstResponder etc.) move to their proper home.
Chess on iOS-sim: board renders, full state intact. 183 example
tests + zig build test green.
This commit is contained in:
@@ -735,8 +735,17 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
_ = c.LLVMBuildRetVoid(self.builder);
|
||||
|
||||
// Register in @llvm.global_ctors + inject into main for ORC JIT.
|
||||
self.appendModuleCtor(ctor, ctor_ty);
|
||||
// Inject the call into main's entry block ONLY — skip
|
||||
// @llvm.global_ctors. Apple's frameworks (UIKit on iOS,
|
||||
// AppKit on macOS) register their Obj-C classes during
|
||||
// dyld's image-init phase, which overlaps global_ctors. If
|
||||
// we ran there too, `objc_getClass(\"UIResponder\")` would
|
||||
// return null and `objc_allocateClassPair(null, ...)` would
|
||||
// crash inside objc_registerClassPair. main's entry runs
|
||||
// AFTER dyld's framework init is complete but BEFORE user
|
||||
// code (UIApplicationMain), so the runtime sees the parent
|
||||
// class properly.
|
||||
self.injectCtorIntoMain(ctor, ctor_ty);
|
||||
|
||||
_ = i32_ty;
|
||||
}
|
||||
@@ -778,6 +787,30 @@ pub const LLVMEmitter = struct {
|
||||
/// global if not present, extending the array if so) AND inject a
|
||||
/// direct call from `main`'s entry block so the ORC JIT path runs
|
||||
/// the constructor too.
|
||||
/// Inject a call to `ctor()` at the start of `main`'s entry block
|
||||
/// (past any existing init calls). Used by class-pair init etc.
|
||||
/// that need to run BEFORE user code but AFTER dyld's framework
|
||||
/// load — global_ctors is too early because Apple frameworks
|
||||
/// (UIKit etc.) register their Obj-C classes during their own
|
||||
/// init phase that overlaps ours.
|
||||
fn injectCtorIntoMain(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void {
|
||||
const main_z = "main";
|
||||
const main_fn = c.LLVMGetNamedFunction(self.llvm_module, main_z);
|
||||
if (main_fn == null) return;
|
||||
const entry_bb = c.LLVMGetEntryBasicBlock(main_fn);
|
||||
var insert_before = c.LLVMGetFirstInstruction(entry_bb);
|
||||
while (insert_before != null) : (insert_before = c.LLVMGetNextInstruction(insert_before)) {
|
||||
if (c.LLVMGetInstructionOpcode(insert_before) != c.LLVMCall) break;
|
||||
}
|
||||
if (insert_before != null) {
|
||||
c.LLVMPositionBuilderBefore(self.builder, insert_before);
|
||||
} else {
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, entry_bb);
|
||||
}
|
||||
var no_args: [0]c.LLVMValueRef = .{};
|
||||
_ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, "");
|
||||
}
|
||||
|
||||
fn appendModuleCtor(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void {
|
||||
const i32_ty = self.cached_i32;
|
||||
const ptr_ty = self.cached_ptr;
|
||||
|
||||
Reference in New Issue
Block a user