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:
agra
2026-05-26 07:32:57 +03:00
parent ea32f8a27a
commit 66f84f67b8
4 changed files with 222 additions and 63 deletions

View File

@@ -140,7 +140,17 @@ UIScreen :: #foreign #objc_class("UIScreen") {
bounds :: (self: *Self) -> CGRect;
}
// UIResponder is the root for keyboard / touch / focus dispatch.
// Most UIKit classes inherit from it; sx-defined classes that
// participate in lifecycle callbacks (delegates, scene delegates)
// extend it so the runtime picks up the responder-chain behavior.
UIResponder :: #foreign #objc_class("UIResponder") {
becomeFirstResponder :: (self: *Self) -> s8;
resignFirstResponder :: (self: *Self) -> s8;
}
UIView :: #foreign #objc_class("UIView") {
#extends UIResponder;
safeAreaInsets :: (self: *Self) -> UIEdgeInsets;
addSubview :: (self: *Self, view: *void);
layer :: (self: *Self) -> *CALayer;
@@ -162,12 +172,29 @@ UIViewController :: #foreign #objc_class("UIViewController") {
}
UITextField :: #foreign #objc_class("UITextField") {
alloc :: () -> *UITextField;
init :: (self: *Self) -> *UITextField;
// Inherited from UIResponder via the runtime; declared here directly
// until `#extends UIResponder` lands (Phase 3.4).
becomeFirstResponder :: (self: *Self) -> s8;
resignFirstResponder :: (self: *Self) -> s8;
#extends UIResponder;
alloc :: () -> *UITextField;
init :: (self: *Self) -> *UITextField;
}
// SxAppDelegate — UIApplicationMain's principal class. Replaces the
// M3.1 hand-rolled objc_allocateClassPair + class_addMethod sequence
// in uikit_register_classes. The method bodies forward to the
// existing legacy IMP free functions so we don't have to inline 70+
// lines of keyboard-frame logic here.
SxAppDelegate :: #objc_class("SxAppDelegate") {
#extends UIResponder;
application_didFinishLaunchingWithOptions :: (self: *Self, app: *void, opts: *void) -> BOOL {
return xx uikit_did_finish_launching(xx self, xx 0, app, opts);
}
sxKeyboardWillChangeFrame :: (self: *Self, notification: *void) {
uikit_keyboard_will_change_frame(xx self, xx 0, notification);
}
alloc :: () -> *SxAppDelegate;
init :: (self: *SxAppDelegate) -> *SxAppDelegate;
}
// GLenum constants for renderbuffer/framebuffer setup that aren't in opengl.sx's
@@ -401,24 +428,19 @@ uikit_chdir_to_bundle :: () {
uikit_register_classes :: () {
inline if OS == .ios {
UIResponder := objc_getClass("UIResponder".ptr);
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
// SxAppDelegate is now declared as `#objc_class("SxAppDelegate")`
// (M3.1) — the compiler synthesises its IMPs and class-pair
// registration at module init. The old hand-rolled
// objc_allocateClassPair + class_addMethod sequence is gone.
class_addMethod(SxAppDelegate,
sel_registerName("application:didFinishLaunchingWithOptions:".ptr),
xx uikit_did_finish_launching, "c@:@@".ptr);
class_addMethod(SxAppDelegate,
sel_registerName("sxKeyboardWillChangeFrame:".ptr),
xx uikit_keyboard_will_change_frame, "v@:@".ptr);
objc_registerClassPair(SxAppDelegate);
UIResponder_cls := objc_getClass("UIResponder".ptr);
// SxSceneDelegate handles the per-scene UI setup. iOS 13+ scene-based
// lifecycle: didFinishLaunching is too early for the window — the
// UIWindowScene doesn't connect until `scene:willConnectTo:options:`.
// The class is named in Info.plist's UIApplicationSceneManifest →
// UISceneDelegateClassName.
SxSceneDelegate := objc_allocateClassPair(UIResponder, "SxSceneDelegate".ptr, 0);
SxSceneDelegate := objc_allocateClassPair(UIResponder_cls, "SxSceneDelegate".ptr, 0);
class_addMethod(SxSceneDelegate,
sel_registerName("scene:willConnectToSession:options:".ptr),