// Obj-C runtime FFI primitives. // // objc_msgSend has the standard ARM64 calling convention (no varargs path). // Each call site must invoke through a function pointer of the *exact* // argument and return shape. The idiom: // // msg_fn : (recv: *void, sel: *void, arg: [*]u8) -> *void = xx objc_msgSend; // result := msg_fn(receiver, selector, c_string); // ─── Obj-C primitive type aliases ─────────────────────────────────────── // Named stand-ins for the three opaque Obj-C runtime types. They all // resolve to `*void` at the LLVM layer (no runtime cost) but improve // readability in foreign-class declarations and call sites. // // id — any Obj-C instance pointer // Class — a class object pointer // SEL — a registered selector // // `Class(T)` parameterization (phantom-typed, with `#extends`-aware // covariance) is a follow-up — needs compiler-level type-check support. // For now, `Class` alone is the only form; assignments are not checked // against the referent's class hierarchy. id :: *void; Class :: *void; SEL :: *void; // Apple's `BOOL` is a signed char (NOT sx's built-in `bool`, which is // LLVM `i1`). Obj-C method signatures that take or return `BOOL` cross // the FFI boundary as `s8`. BOOL :: s8; // On macOS libobjc is auto-loaded by libSystem; on iOS it isn't, so we // link it explicitly. Foundation registers NSString etc. with the runtime, // also auto-loaded on macOS and required as an explicit framework on iOS. objc :: #library "objc"; #framework "Foundation"; objc_getClass :: (name: [*]u8) -> *void #foreign objc; objc_lookUpClass :: (name: [*]u8) -> *void #foreign objc; sel_registerName :: (name: [*]u8) -> *void #foreign objc; class_createInstance :: (cls: *void, extra: usize) -> *void #foreign objc; object_getClass :: (obj: *void) -> *void #foreign objc; object_getIvar :: (obj: *void, ivar: *void) -> *void #foreign objc; object_setIvar :: (obj: *void, ivar: *void, val: *void) #foreign objc; // Declared with the simplest non-variadic shape. Cast per call site. objc_msgSend :: (recv: *void, sel: *void) -> *void #foreign objc; // ─── Dynamic class registration ───────────────────────────────────────── // Define a new Obj-C class at runtime: allocate the pair, attach methods + // protocols, then finalize with `objc_registerClassPair`. The class is then // usable via `class_createInstance` and Obj-C dispatch. // // IMPs (method implementations) are function pointers with the implicit // Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with // `callconv(.c)` so they land args in the standard C registers. // // Method type encoding strings follow Apple's runtime DSL: // v = void c = char/BOOL i = int l = long f = float d = double // @ = id (object) : = SEL # = Class // Return type comes first, then receiver (`@`), then `_cmd` (`:`), then args. // Examples: // "v@:" -> void method(id, SEL) // "c@:" -> BOOL method(id, SEL) // "@@:@" -> id method(id, SEL, id) // "B@:@@" -> BOOL method(id, SEL, id, id) objc_allocateClassPair :: (super: *void, name: [*]u8, extra: usize) -> *void #foreign objc; class_addMethod :: (cls: *void, sel: *void, imp: *void, types: [*]u8) -> bool #foreign objc; class_addProtocol :: (cls: *void, proto: *void) -> bool #foreign objc; objc_getProtocol :: (name: [*]u8) -> *void #foreign objc; objc_registerClassPair :: (cls: *void) -> void #foreign objc; // Foundation C-API helpers (Foundation is already linked above via #framework). // NSLog takes an NSString format; the variadic tail is not exposed here. NSLog :: (fmt: *NSString) #foreign; // ─── Convenience helpers ──────────────────────────────────────────────── // These hide the typed-fn-pointer cast for the most common shapes. They // re-register selectors per call — if you're in a tight loop, cache the SEL. // Wrap a C string in an autoreleased NSString. ns_string :: (s: [*]u8) -> *void { cls := objc_getClass("NSString".ptr); sel := sel_registerName("stringWithUTF8String:".ptr); fn_ptr : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend; return fn_ptr(cls, sel, s); } // View an NSString's bytes as a C string. The returned pointer's lifetime is // tied to the NSString; don't free it. c_string :: (ns: *void) -> [*]u8 { sel := sel_registerName("UTF8String".ptr); fn_ptr : (*void, *void) -> [*]u8 callconv(.c) = xx objc_msgSend; return fn_ptr(ns, sel); } // ─── NSObject (Phase 4 / M4.A) ─────────────────────────────────────────── // Root of every Obj-C class hierarchy. Apple's runtime supplies the IMPs; // sx declares the method surface so user code can write // `defer view.release();` and `view.retain()` directly instead of going // through `objc_msgSend` casts. M2.3's `#extends`-aware dispatch finds // these methods automatically once a class roots its `#extends` chain at // NSObject (foreign classes in uikit.sx etc. add `#extends NSObject;` to // inherit the surface). // // `release` is NOT a reserved keyword in sx — modern clang ARC bans // user-source calls to it (the ARC compiler emits them automatically), but // sx isn't under clang ARC. Calling `view.release()` here is equivalent to // pre-ARC Obj-C source code: dispatches through the runtime, decrements the // refcount, fires `-dealloc` at zero. NSObject :: #foreign #objc_class("NSObject") { alloc :: () -> *NSObject; init :: (self: *Self) -> *NSObject; new :: () -> *NSObject; // shorthand for [[Cls alloc] init] retain :: (self: *Self) -> *Self; release :: (self: *Self); autorelease :: (self: *Self) -> *Self; class :: () -> *void; // metaclass query — `Cls.class()` description :: (self: *Self) -> *void; // returns *NSString hash :: (self: *Self) -> u64; isEqual :: (self: *Self, other: *void) -> BOOL; isKindOfClass :: (self: *Self, cls: *void) -> BOOL; respondsToSelector :: (self: *Self, sel: *void) -> BOOL; } // ─── NSString ──────────────────────────────────────────────────────────── // Foundation's immutable string. `ns_string` builds an autoreleased instance // from a C string; the `Into` impl lets a string literal flow into any // `*NSString` slot via `xx`, e.g. `dict.objectForKey(xx "SomeKey")`. NSString :: #foreign #objc_class("NSString") { #extends NSObject; UTF8String :: (self: *Self) -> [*]u8; } // `self.ptr` must be NUL-terminated. String literals are; an arbitrary // substring/built `string` may not be, so only pass literals (or otherwise // NUL-terminated slices) through this conversion. impl Into(*NSString) for string { convert :: (self: string) -> *NSString { return xx ns_string(self.ptr); } } // ─── Autoreleasepool (M4.A) ────────────────────────────────────────────── // Foundation factory methods (`NSString.stringWithUTF8String:`, // `[NSArray array]`, ...) return autoreleased objects — valid until the // current pool drains. Wrap such code in `autoreleasepool(() => { ... })` // so the pool drains deterministically at block end. // // Stdlib helper, not a language keyword. The closure call adds a frame — // for hot loops, inline the push/defer-pop pattern manually. objc_autoreleasePoolPush :: () -> *void #foreign objc; objc_autoreleasePoolPop :: (pool: *void) #foreign objc; autoreleasepool :: (body: Closure()) { pool := objc_autoreleasePoolPush(); defer objc_autoreleasePoolPop(pool); body(); }