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:
@@ -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.0–M1.3 + M2.1–M2.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.6–1.14)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user