ffi M3.2: SxSceneDelegate migrated + #implements protocol conformance

Migrates SxSceneDelegate from the hand-rolled
objc_allocateClassPair + class_addMethod + class_addProtocol
sequence to the declarative form:

  SxSceneDelegate :: #objc_class("SxSceneDelegate") {
      #extends UIResponder;
      #implements UISceneDelegate;
      #implements UIWindowSceneDelegate;

      scene_willConnectToSession_options :: (self, scene, session, options) { ... }
      window    :: (self) -> *void { ... }
      setWindow :: (self, w) { ... }
  }

emit_llvm now honors '#implements' in the class-pair init
constructor — for each #implements ProtocolAlias on the cache
entry's AST, emit before objc_registerClassPair:

  proto = objc_getProtocol("ProtocolName")
  class_addProtocol(cls, proto)

iOS checks 'class_conformsToProtocol' when instantiating scene
delegates; without the conformance the runtime silently rejects
the class and a default scene with no delegate gets created
instead. The protocol-getter returns null on dead-strip /
runtime mismatch (rare but possible) — the runtime treats
class_addProtocol(cls, null) as a no-op, so no explicit null
check needed.

Method bodies forward to the existing legacy free IMP functions
(uikit_scene_will_connect, uikit_window_getter,
uikit_window_setter) so we don't have to inline the scene-
connect setup logic (~80 lines).

uikit_register_classes is now tiny — just the two remaining
view-class helpers (M3.3 SxGLView + M3.4 SxMetalView). M3.5
deletes the function entirely once those land.

Chess on iOS-sim: board renders, scene delegate fires, touch
events route correctly. 183 example tests + zig build test
green.
This commit is contained in:
agra
2026-05-26 07:37:14 +03:00
parent 66f84f67b8
commit 066840d9e0
3 changed files with 56 additions and 43 deletions

View File

@@ -654,6 +654,28 @@ pub const LLVMEmitter = struct {
_ = c.LLVMBuildCall2(self.builder, add_method_ty, add_method_fn, &add_args, 4, "");
}
// M2.3 / M3.2 — register `#implements` protocol conformances
// BEFORE objc_registerClassPair. iOS checks
// `class_conformsToProtocol` when instantiating scene
// delegates and other protocol-typed callbacks; without
// these the runtime silently rejects the class.
//
// The protocol may not be present on every SDK / runtime
// (dead-strip pruning, version skew), so `objc_getProtocol`
// returning null is non-fatal — skip the addProtocol call.
const get_proto_fn, const get_proto_ty = self.lazyDeclareCRuntime("objc_getProtocol", &[_]c.LLVMTypeRef{ptr_ty}, ptr_ty, 0);
const add_proto_fn, const add_proto_ty = self.lazyDeclareCRuntime("class_addProtocol", &[_]c.LLVMTypeRef{ ptr_ty, ptr_ty }, i8_ty, 0);
for (fcd.members) |m| switch (m) {
.implements => |proto_alias| {
const proto_str_global = self.emitPrivateCString(proto_alias, "OBJC_PROTOCOL_NAME_");
var gp_args: [1]c.LLVMValueRef = .{proto_str_global};
const proto_val = c.LLVMBuildCall2(self.builder, get_proto_ty, get_proto_fn, &gp_args, 1, "proto");
var ap_args: [2]c.LLVMValueRef = .{ cls_val, proto_val };
_ = c.LLVMBuildCall2(self.builder, add_proto_ty, add_proto_fn, &ap_args, 2, "");
},
else => {},
};
// objc_registerClassPair(cls)
var reg_args: [1]c.LLVMValueRef = .{cls_val};
_ = c.LLVMBuildCall2(self.builder, register_ty, register_fn, &reg_args, 1, "");