ffi uikit cleanup: helpers → UIKitPlatform methods + declarative layerClass

Three threads, one commit because they're entangled:

1. Helper free functions on `*UIKitPlatform` (refresh_safe_insets,
   read_screen_scale, create_gl_context, setup_renderbuffer,
   present_renderbuffer, compute_layer_pixel_size) → methods on the
   `impl Platform for UIKitPlatform` block. IMP-shape trampolines
   (`uikit_keyboard_will_change_frame`, `uikit_scene_will_connect[_ios]`,
   `uikit_gl_view_tick/layout/touches_*`, `uikit_subscribe_keyboard_notifications`)
   also collapse into methods on UIKitPlatform — the
   `(self: *void, _cmd: *void, ...)` form is no longer needed since
   M3 made the #objc_class trampolines compiler-synthesized. Class
   method bodies in SxAppDelegate / SxSceneDelegate / SxGLView /
   SxMetalView now read `if g_uikit_plat == null { return; }
   g_uikit_plat.x();` — no more `xx self, xx 0` casts at every IMP
   call site.

2. Declarative `layerClass` form. SxGLView and SxMetalView promote
   from the M2.1(a) constant-with-runtime-string-lookup workaround
   (`layerClass :: *void = objc_getClass("CAEAGLLayer".ptr);`) to
   the class-method expression-body form
   (`layerClass :: () => CAEAGLLayer.class();`). Type stays `*void`
   until M1.1.b lands `Class(T)` parameterisation; the value side
   already matches the plan. Backing this: foreign-class declarations
   for CAEAGLLayer (extended with `class :: () -> *void;`) and a new
   CAMetalLayer foreign-class declaration alongside it. Both
   `#extends CALayer` so the dispatch chain knows about the parent.

3. Optional-shape idiom pass on uikit.sx. `xx`-as-optional-wrap on
   field assignments (`plat.gl_ctx = xx ctx`, `plat.text_field = xx tf`,
   `plat.display_link = xx link`) dropped — implicit `T → ?T` does
   the right thing. `!` force-unwraps replaced with `if val := opt
   { ... }` safe-narrowing (the keyboard handler, the GL-context
   read in setup/present renderbuffer, the gl_view read in scene
   bootstrap). `orelse` (Zig keyword) that briefly snuck into the
   keyboard handler removed in favour of the `if win := plat.window`
   narrowing pattern. Result: no `xx` casts left on the implicit
   T→?T path; all optional access goes through `if val :=`.

IR snapshots `ffi-objc-call-06-sret-return.ir` and
`ffi-objc-dsl-07-mangling-table.ir` refresh to pick up the new
`object_getIvar` / `object_setIvar` runtime-helper declarations
introduced when M1.2 A.3 made instance-method bodies route field
access through the state ivar.

Chess on iOS-sim green throughout. 184/184 example tests pass.
This commit is contained in:
agra
2026-05-26 16:42:57 +03:00
parent a923b6f6f0
commit 9fbc24a602
4 changed files with 507 additions and 468 deletions

View File

@@ -6,6 +6,51 @@ add a test and make it pass — that's two commits).
## Last completed step
**M1.2 A.0 — `objc_defined_class_cache` + scan-pass registration**
(`61a2593`).
Added an insertion-ordered cache on `Module` for sx-defined Obj-C
classes (every `#objc_class("Cls") { ... }` declaration WITHOUT
`#foreign`). `registerForeignClassDecl` appends the entry alongside
its existing `foreign_class_map` insert.
```zig
pub const ObjcDefinedClassEntry = struct {
name: []const u8,
decl: *const ast.ForeignClassDecl,
};
```
Pointer back to the AST lets later A.* passes re-walk `members`
without duplicating data. Insertion order matters because
class-pair init constructors (A.4) must register parents before
children — `objc_allocateClassPair(super, ...)` resolves super by
lookup. Infrastructure only; populated but not yet read.
170 example tests + `zig build test` green.
---
**M1.1 first pass — id / Class / SEL / BOOL aliases** (`d9dbdad`).
Added stand-ins for the opaque Obj-C runtime types to
`library/modules/std/objc.sx`: `id`, `Class`, `SEL` resolve to
`*void`; `BOOL` to `s8`. All zero-cost at the LLVM layer; the
header's old caveat about lacking aliases is gone.
`141-objc-type-aliases.sx` exercises them against the real macOS
Obj-C runtime via `isKindOfClass`.
**Deferred to M1.1.b**: `Class(T)` parameterization with
`#extends`-aware covariance + `instancetype` per-decl
substitution. Both need compiler-level type-check work beyond
stdlib aliases. The current sx type system doesn't enforce
nominal identity on parametric struct instantiations (verified
probe: `Class(NSString)` flows into `Class(CALayer)` parameter
without error), so a stdlib-only Class(T) would give syntax with
no safety. Punted to a focused later slice.
---
**M1.0 — Expression-bodied function declarations**
(3 commits: `6c95b2a`, `4a048d3`, `86c1127`).
@@ -177,29 +222,55 @@ plus 2 codegen fixes surfaced along the way.**
## Current state
- 169/169 example tests pass; `zig build test` green.
- Phase 3.0/3.1/3.2 (Obj-C DSL dispatch + selector mangling +
selector override + uikit.sx C1-C5 cluster migrations) all
landed. M1.0 (expression-bodied functions) just landed.
- Phase 3.0/3.1/3.2 + M1.0M1.3 + M2.1M2.3 + M3 (full
retire-`uikit_register_classes`) all landed.
- Chess on macOS / iOS-sim / Android all build and run.
- `library/modules/platform/uikit.sx` follow-up cleanup just
shipped: every `plat: *UIKitPlatform` helper and every
`(self: *void, _cmd: *void, ...)` trampoline is now a method
on `UIKitPlatform`. Method bodies in SxAppDelegate /
SxSceneDelegate / SxGLView / SxMetalView call `g_uikit_plat.x()`
for the shared paths and inline the trivial bridges (no more
`xx self, xx 0` casts at IMP-call sites). `layerClass` uses the
declarative `() => CAEAGLLayer.class()` / `CAMetalLayer.class()`
form on top of new foreign-class declarations for both layer
types.
- **issue-0044 FIXED.** The root cause was a `target_type` leak in
`resolveCallParamTypes` for UFCS calls on foreign-class
(`#objc_class` / `#foreign #objc_class`) receivers. With no
param-types resolved for the receiver's method, `self.target_type`
retained the enclosing fn's return type — and a `BOOL`-returning
method's `xx ptr` inside an Obj-C call site silently truncated the
pointer to i8. Fix at
[src/ir/lower.zig:8617-8639](../src/ir/lower.zig#L8617) walks
`foreign_class_map` + `findForeignMethodInChain` for the method's
declared param types. Regression test
[examples/issue-0044.sx](../examples/issue-0044.sx).
184/184 example tests green; chess on iOS-sim green.
- Active forward plan: 6-month Obj-C FFI roadmap at
`~/.claude/plans/lets-see-options-for-merry-dijkstra.md`.
## Next step (M1.1 — Foreign type aliases)
## Next step (M1.2 A.1 — type-encoding derivation table)
Introduce sx-side typed aliases for the Obj-C primitives that
today are `*void`: `Class(T)` (phantom-parameterized), `id`, `SEL`,
`BOOL`, `instancetype`. Per the roadmap, the load-bearing piece
is `Class(T)`: a phantom-typed pointer alias enabling type-safe
factory returns like
`layerClass :: Class(CALayer) = CAEAGLLayer.class();` (the M2.1(a)
class-constant form). Plain `Class``Class(NSObject)` sugar.
`Cls.class()` returns `Class(Cls)`. Covariant on `#extends`.
The synthesized `+alloc` (A.5), `-dealloc` (A.6), and every
instance-method IMP (A.2) need to call `class_addMethod(cls, sel,
imp, types)` with a type-encoding string in Apple's runtime DSL:
Files to touch: [src/parser.zig](../src/parser.zig) (parser
support for `Class(T)` type syntax),
[src/ir/lower.zig](../src/ir/lower.zig) (alias resolution +
covariance check). LLVM layer unchanged — these are phantom-typed,
underlying representation stays `*void`.
- `v` = void, `i` = s32, `q` = s64, `f` = f32, `d` = f64, `B` = bool,
- `c` = s8/BOOL, `C` = u8, `s` = s16, `S` = u16, `l/L` = long,
`Q` = u64, `*` = `[*]u8`,
- `@` = id (object), `#` = Class, `:` = SEL, `^v` = `*void`.
- Struct: `{Name=field1field2...}`.
A.1 = `objcTypeEncodingFromSignature` helper in
[src/ir/lower.zig](../src/ir/lower.zig). Inputs: receiver-as-`@`,
`_cmd` selector slot `:`, then return type + arg types from the
IR signature. Lookup table over `TypeId`. No emission yet — A.1
is a pure helper that A.2-A.6 will call.
Bounded slice: probably 100-200 lines of Zig, one-pass switch
over TypeId. No cadence-rule test needed (helper has no observable
output on its own; tested via integration with A.2+).
## Phase 1B complete (1.61.14)

File diff suppressed because it is too large Load Diff

View File

@@ -1800,6 +1800,12 @@ declare ptr @class_createInstance(ptr, i64) #0
; Function Attrs: nounwind
declare ptr @object_getClass(ptr) #0
; Function Attrs: nounwind
declare ptr @object_getIvar(ptr, ptr) #0
; Function Attrs: nounwind
declare void @object_setIvar(ptr, ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @objc_msgSend(ptr, ptr) #0

View File

@@ -765,6 +765,12 @@ declare ptr @class_createInstance(ptr, i64) #0
; Function Attrs: nounwind
declare ptr @object_getClass(ptr) #0
; Function Attrs: nounwind
declare ptr @object_getIvar(ptr, ptr) #0
; Function Attrs: nounwind
declare void @object_setIvar(ptr, ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @objc_msgSend(ptr, ptr) #0