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:
@@ -140,7 +140,17 @@ UIScreen :: #foreign #objc_class("UIScreen") {
|
|||||||
bounds :: (self: *Self) -> CGRect;
|
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") {
|
UIView :: #foreign #objc_class("UIView") {
|
||||||
|
#extends UIResponder;
|
||||||
safeAreaInsets :: (self: *Self) -> UIEdgeInsets;
|
safeAreaInsets :: (self: *Self) -> UIEdgeInsets;
|
||||||
addSubview :: (self: *Self, view: *void);
|
addSubview :: (self: *Self, view: *void);
|
||||||
layer :: (self: *Self) -> *CALayer;
|
layer :: (self: *Self) -> *CALayer;
|
||||||
@@ -162,12 +172,29 @@ UIViewController :: #foreign #objc_class("UIViewController") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UITextField :: #foreign #objc_class("UITextField") {
|
UITextField :: #foreign #objc_class("UITextField") {
|
||||||
alloc :: () -> *UITextField;
|
#extends UIResponder;
|
||||||
init :: (self: *Self) -> *UITextField;
|
alloc :: () -> *UITextField;
|
||||||
// Inherited from UIResponder via the runtime; declared here directly
|
init :: (self: *Self) -> *UITextField;
|
||||||
// until `#extends UIResponder` lands (Phase 3.4).
|
}
|
||||||
becomeFirstResponder :: (self: *Self) -> s8;
|
|
||||||
resignFirstResponder :: (self: *Self) -> s8;
|
// 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
|
// GLenum constants for renderbuffer/framebuffer setup that aren't in opengl.sx's
|
||||||
@@ -401,24 +428,19 @@ uikit_chdir_to_bundle :: () {
|
|||||||
|
|
||||||
uikit_register_classes :: () {
|
uikit_register_classes :: () {
|
||||||
inline if OS == .ios {
|
inline if OS == .ios {
|
||||||
UIResponder := objc_getClass("UIResponder".ptr);
|
// SxAppDelegate is now declared as `#objc_class("SxAppDelegate")`
|
||||||
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
|
// (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,
|
UIResponder_cls := objc_getClass("UIResponder".ptr);
|
||||||
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);
|
|
||||||
|
|
||||||
// SxSceneDelegate handles the per-scene UI setup. iOS 13+ scene-based
|
// SxSceneDelegate handles the per-scene UI setup. iOS 13+ scene-based
|
||||||
// lifecycle: didFinishLaunching is too early for the window — the
|
// lifecycle: didFinishLaunching is too early for the window — the
|
||||||
// UIWindowScene doesn't connect until `scene:willConnectTo:options:`.
|
// UIWindowScene doesn't connect until `scene:willConnectTo:options:`.
|
||||||
// The class is named in Info.plist's UIApplicationSceneManifest →
|
// The class is named in Info.plist's UIApplicationSceneManifest →
|
||||||
// UISceneDelegateClassName.
|
// UISceneDelegateClassName.
|
||||||
SxSceneDelegate := objc_allocateClassPair(UIResponder, "SxSceneDelegate".ptr, 0);
|
SxSceneDelegate := objc_allocateClassPair(UIResponder_cls, "SxSceneDelegate".ptr, 0);
|
||||||
|
|
||||||
class_addMethod(SxSceneDelegate,
|
class_addMethod(SxSceneDelegate,
|
||||||
sel_registerName("scene:willConnectToSession:options:".ptr),
|
sel_registerName("scene:willConnectToSession:options:".ptr),
|
||||||
|
|||||||
@@ -735,8 +735,17 @@ pub const LLVMEmitter = struct {
|
|||||||
}
|
}
|
||||||
_ = c.LLVMBuildRetVoid(self.builder);
|
_ = c.LLVMBuildRetVoid(self.builder);
|
||||||
|
|
||||||
// Register in @llvm.global_ctors + inject into main for ORC JIT.
|
// Inject the call into main's entry block ONLY — skip
|
||||||
self.appendModuleCtor(ctor, ctor_ty);
|
// @llvm.global_ctors. Apple's frameworks (UIKit on iOS,
|
||||||
|
// AppKit on macOS) register their Obj-C classes during
|
||||||
|
// dyld's image-init phase, which overlaps global_ctors. If
|
||||||
|
// we ran there too, `objc_getClass(\"UIResponder\")` would
|
||||||
|
// return null and `objc_allocateClassPair(null, ...)` would
|
||||||
|
// crash inside objc_registerClassPair. main's entry runs
|
||||||
|
// AFTER dyld's framework init is complete but BEFORE user
|
||||||
|
// code (UIApplicationMain), so the runtime sees the parent
|
||||||
|
// class properly.
|
||||||
|
self.injectCtorIntoMain(ctor, ctor_ty);
|
||||||
|
|
||||||
_ = i32_ty;
|
_ = i32_ty;
|
||||||
}
|
}
|
||||||
@@ -778,6 +787,30 @@ pub const LLVMEmitter = struct {
|
|||||||
/// global if not present, extending the array if so) AND inject a
|
/// global if not present, extending the array if so) AND inject a
|
||||||
/// direct call from `main`'s entry block so the ORC JIT path runs
|
/// direct call from `main`'s entry block so the ORC JIT path runs
|
||||||
/// the constructor too.
|
/// the constructor too.
|
||||||
|
/// Inject a call to `ctor()` at the start of `main`'s entry block
|
||||||
|
/// (past any existing init calls). Used by class-pair init etc.
|
||||||
|
/// that need to run BEFORE user code but AFTER dyld's framework
|
||||||
|
/// load — global_ctors is too early because Apple frameworks
|
||||||
|
/// (UIKit etc.) register their Obj-C classes during their own
|
||||||
|
/// init phase that overlaps ours.
|
||||||
|
fn injectCtorIntoMain(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void {
|
||||||
|
const main_z = "main";
|
||||||
|
const main_fn = c.LLVMGetNamedFunction(self.llvm_module, main_z);
|
||||||
|
if (main_fn == null) return;
|
||||||
|
const entry_bb = c.LLVMGetEntryBasicBlock(main_fn);
|
||||||
|
var insert_before = c.LLVMGetFirstInstruction(entry_bb);
|
||||||
|
while (insert_before != null) : (insert_before = c.LLVMGetNextInstruction(insert_before)) {
|
||||||
|
if (c.LLVMGetInstructionOpcode(insert_before) != c.LLVMCall) break;
|
||||||
|
}
|
||||||
|
if (insert_before != null) {
|
||||||
|
c.LLVMPositionBuilderBefore(self.builder, insert_before);
|
||||||
|
} else {
|
||||||
|
c.LLVMPositionBuilderAtEnd(self.builder, entry_bb);
|
||||||
|
}
|
||||||
|
var no_args: [0]c.LLVMValueRef = .{};
|
||||||
|
_ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, "");
|
||||||
|
}
|
||||||
|
|
||||||
fn appendModuleCtor(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void {
|
fn appendModuleCtor(self: *LLVMEmitter, ctor: c.LLVMValueRef, ctor_ty: c.LLVMTypeRef) void {
|
||||||
const i32_ty = self.cached_i32;
|
const i32_ty = self.cached_i32;
|
||||||
const ptr_ty = self.cached_ptr;
|
const ptr_ty = self.cached_ptr;
|
||||||
|
|||||||
177
src/ir/lower.zig
177
src/ir/lower.zig
@@ -1657,16 +1657,33 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.field_access => |fa| {
|
.field_access => |fa| {
|
||||||
// M2.2 — `obj.field = val` for an Obj-C `#property`
|
// M2.2 — `obj.field = val` for an Obj-C `#property` field
|
||||||
// field dispatches as `[obj setField:val]` through
|
// dispatches via objc_msgSend `setField:`. Skip struct-
|
||||||
// objc_msgSend. Skip the struct-pointer / GEP path
|
// pointer / GEP entirely; receivers are opaque Obj-C ids.
|
||||||
// entirely; receivers are opaque Obj-C ids.
|
// Compound ops on properties are deferred (need load-via-
|
||||||
|
// getter + op + store-via-setter — Month 4 ARC territory).
|
||||||
if (asgn.op == .assign) {
|
if (asgn.op == .assign) {
|
||||||
if (self.lookupObjcPropertyOnPointer(fa.object, fa.field)) |prop| {
|
if (self.lookupObjcPropertyOnPointer(fa.object, fa.field)) |prop| {
|
||||||
self.lowerObjcPropertySetter(fa.object, prop, val);
|
self.lowerObjcPropertySetter(fa.object, prop, val);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// M1.2 A.3 — `self.field [op]= val` on a sx-defined Obj-C
|
||||||
|
// class instance field (NOT a #property): write through
|
||||||
|
// the __sx_state ivar. Handles plain assignment AND
|
||||||
|
// compound ops (+=, -=, etc.) via storeOrCompound.
|
||||||
|
if (self.lookupObjcDefinedStateFieldOnPointer(fa.object, fa.field)) |info| {
|
||||||
|
const obj_ref = self.lowerExpr(fa.object);
|
||||||
|
const state_ptr = self.lowerObjcDefinedStateForObj(obj_ref, info.fcd) orelse return;
|
||||||
|
const ptr_void = self.module.types.ptrTo(.void);
|
||||||
|
const field_addr = self.builder.emit(.{ .struct_gep = .{
|
||||||
|
.base = state_ptr,
|
||||||
|
.field_index = info.field_idx,
|
||||||
|
.base_type = info.state_ty,
|
||||||
|
} }, ptr_void);
|
||||||
|
self.storeOrCompound(field_addr, val, asgn.op, info.field_ty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var obj_ptr = self.lowerExprAsPtr(fa.object);
|
var obj_ptr = self.lowerExprAsPtr(fa.object);
|
||||||
var obj_ty = self.inferExprType(fa.object);
|
var obj_ty = self.inferExprType(fa.object);
|
||||||
@@ -3610,6 +3627,16 @@ pub const Lowering = struct {
|
|||||||
return self.lowerObjcPropertyGetter(fa.object, prop, fa.field, span);
|
return self.lowerObjcPropertyGetter(fa.object, prop, fa.field, span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// M1.2 A.3 — `self.field` (or `obj.field`) on a *sx-defined-class
|
||||||
|
// pointer for a plain instance field (NOT a #property) lowers as
|
||||||
|
// `object_getIvar(obj, load(__<Cls>_state_ivar))` + struct_gep on
|
||||||
|
// the state struct + load. The receiver is the opaque Obj-C id
|
||||||
|
// (matching Apple's `self` semantics); the state lives in the
|
||||||
|
// hidden `__sx_state` ivar.
|
||||||
|
if (self.lookupObjcDefinedStateFieldOnPointer(fa.object, fa.field)) |info| {
|
||||||
|
return self.lowerObjcDefinedStateFieldRead(fa.object, info);
|
||||||
|
}
|
||||||
|
|
||||||
var obj = self.lowerExpr(fa.object);
|
var obj = self.lowerExpr(fa.object);
|
||||||
var obj_ty = self.inferExprType(fa.object);
|
var obj_ty = self.inferExprType(fa.object);
|
||||||
|
|
||||||
@@ -8858,17 +8885,17 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||||
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
||||||
// *Self substitution for sx-defined `#objc_class` method bodies
|
// `*Self` substitution inside foreign-class member declarations
|
||||||
// (M1.2 A.2b). `current_foreign_class` is set by lowerFunction
|
// — both foreign and sx-defined — resolves to the class's own
|
||||||
// and registerObjcDefinedClassMethods when the surrounding
|
// 0-field stub struct (i.e. the opaque Obj-C pointer type).
|
||||||
// function is a class method. `Self` inside the body resolves
|
// This matches the Obj-C idiom where `self` IS the object.
|
||||||
// to the class's hidden state-struct type — `self.field` then
|
// `self.field` access on sx-defined classes is rewritten by
|
||||||
// works as a plain struct field access (M1.2 A.3 "free if
|
// lowerFieldAccess to go through the `__sx_state` ivar
|
||||||
// types align").
|
// (object_getIvar + struct_gep) when needed — see M1.2 A.3.
|
||||||
if (node.data == .type_expr and std.mem.eql(u8, node.data.type_expr.name, "Self")) {
|
if (node.data == .type_expr and std.mem.eql(u8, node.data.type_expr.name, "Self")) {
|
||||||
if (self.current_foreign_class) |fcd| {
|
if (self.current_foreign_class) |fcd| {
|
||||||
if (!fcd.is_foreign and fcd.runtime == .objc_class) {
|
if (fcd.runtime == .objc_class or fcd.runtime == .objc_protocol) {
|
||||||
return self.objcDefinedStateStructType(fcd);
|
return self.foreignClassStructType(fcd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10617,6 +10644,10 @@ pub const Lowering = struct {
|
|||||||
if (self.lookupObjcPropertyOnPointer(fa.object, fa.field)) |prop| {
|
if (self.lookupObjcPropertyOnPointer(fa.object, fa.field)) |prop| {
|
||||||
return self.resolveType(prop.field_type);
|
return self.resolveType(prop.field_type);
|
||||||
}
|
}
|
||||||
|
// M1.2 A.3 — sx-defined class state field returns the field's type.
|
||||||
|
if (self.lookupObjcDefinedStateFieldOnPointer(fa.object, fa.field)) |info| {
|
||||||
|
return info.field_ty;
|
||||||
|
}
|
||||||
|
|
||||||
var obj_ty = self.inferExprType(fa.object);
|
var obj_ty = self.inferExprType(fa.object);
|
||||||
// Auto-deref: if object is a pointer, resolve through it (matches lowerFieldAccess behavior)
|
// Auto-deref: if object is a pointer, resolve through it (matches lowerFieldAccess behavior)
|
||||||
@@ -11681,6 +11712,93 @@ pub const Lowering = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ObjcDefinedStateField = struct {
|
||||||
|
field_ty: TypeId,
|
||||||
|
state_ty: TypeId,
|
||||||
|
field_idx: u32,
|
||||||
|
fcd: *const ast.ForeignClassDecl,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// State-field-access info: if obj_expr is *<sx-defined-class>
|
||||||
|
/// and `field_name` is in the state struct (not a property),
|
||||||
|
/// returns the field's TypeId, the state struct's TypeId, and
|
||||||
|
/// the field's index. M1.2 A.3 supports.
|
||||||
|
fn lookupObjcDefinedStateFieldOnPointer(self: *Lowering, obj_expr: *const ast.Node, field_name: []const u8) ?ObjcDefinedStateField {
|
||||||
|
const obj_ty = self.inferExprType(obj_expr);
|
||||||
|
if (obj_ty.isBuiltin()) return null;
|
||||||
|
const ptr_info = self.module.types.get(obj_ty);
|
||||||
|
if (ptr_info != .pointer) return null;
|
||||||
|
const pointee_info = self.module.types.get(ptr_info.pointer.pointee);
|
||||||
|
if (pointee_info != .@"struct") return null;
|
||||||
|
const struct_name = self.module.types.getString(pointee_info.@"struct".name);
|
||||||
|
const fcd = self.foreign_class_map.get(struct_name) orelse return null;
|
||||||
|
// Only sx-defined Obj-C classes have a state struct. Foreign
|
||||||
|
// classes' fields are purely declaration metadata (no state).
|
||||||
|
if (fcd.is_foreign or fcd.runtime != .objc_class) return null;
|
||||||
|
// Skip property fields — those dispatch via the M2.2 getter/setter
|
||||||
|
// path. Plain instance fields take the ivar+gep path.
|
||||||
|
for (fcd.members) |m| switch (m) {
|
||||||
|
.field => |f| {
|
||||||
|
if (std.mem.eql(u8, f.name, field_name)) {
|
||||||
|
if (f.is_property) return null;
|
||||||
|
const state_ty = self.objcDefinedStateStructType(fcd);
|
||||||
|
const state_info = self.module.types.get(state_ty);
|
||||||
|
if (state_info != .@"struct") return null;
|
||||||
|
const fname_id = self.module.types.internString(f.name);
|
||||||
|
for (state_info.@"struct".fields, 0..) |sf, idx| {
|
||||||
|
if (sf.name == fname_id) {
|
||||||
|
return .{
|
||||||
|
.field_ty = sf.ty,
|
||||||
|
.state_ty = state_ty,
|
||||||
|
.field_idx = @intCast(idx),
|
||||||
|
.fcd = fcd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lower a read of `self.field` (or `obj.field`) on a sx-defined
|
||||||
|
/// Obj-C class: `state = object_getIvar(self, load(ivar_global))`
|
||||||
|
/// then `struct_gep(state, idx)` + load. M1.2 A.3 — the runtime
|
||||||
|
/// hop through the hidden ivar.
|
||||||
|
fn lowerObjcDefinedStateFieldRead(
|
||||||
|
self: *Lowering,
|
||||||
|
obj_expr: *const ast.Node,
|
||||||
|
info: ObjcDefinedStateField,
|
||||||
|
) Ref {
|
||||||
|
const obj_ref = self.lowerExpr(obj_expr);
|
||||||
|
const state_ptr = self.lowerObjcDefinedStateForObj(obj_ref, info.fcd) orelse return Ref.none;
|
||||||
|
const ptr_void = self.module.types.ptrTo(.void);
|
||||||
|
const field_addr = self.builder.emit(.{ .struct_gep = .{
|
||||||
|
.base = state_ptr,
|
||||||
|
.field_index = info.field_idx,
|
||||||
|
.base_type = info.state_ty,
|
||||||
|
} }, ptr_void);
|
||||||
|
return self.builder.load(field_addr, info.field_ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `state = object_getIvar(obj, load(__<Cls>_state_ivar))`. Shared
|
||||||
|
/// helper for state-field read + write (M1.2 A.3).
|
||||||
|
fn lowerObjcDefinedStateForObj(self: *Lowering, obj_ref: Ref, fcd: *const ast.ForeignClassDecl) ?Ref {
|
||||||
|
const ptr_void = self.module.types.ptrTo(.void);
|
||||||
|
const ivar_global_name = std.fmt.allocPrint(self.alloc, "__{s}_state_ivar", .{fcd.name}) catch return null;
|
||||||
|
defer self.alloc.free(ivar_global_name);
|
||||||
|
const ivar_global_id = self.lookupGlobalIdByName(ivar_global_name) orelse return null;
|
||||||
|
const ivar_addr = self.builder.emit(.{ .global_addr = ivar_global_id }, ptr_void);
|
||||||
|
const ivar_handle = self.builder.load(ivar_addr, ptr_void);
|
||||||
|
const get_ivar_fid = self.ensureCRuntimeDecl("object_getIvar", &.{ ptr_void, ptr_void }, ptr_void);
|
||||||
|
const args = self.alloc.alloc(Ref, 2) catch return null;
|
||||||
|
args[0] = obj_ref;
|
||||||
|
args[1] = ivar_handle;
|
||||||
|
return self.builder.emit(.{ .call = .{ .callee = get_ivar_fid, .args = args } }, ptr_void);
|
||||||
|
}
|
||||||
|
|
||||||
/// Lower `obj.field` for an Obj-C `#property` field as
|
/// Lower `obj.field` for an Obj-C `#property` field as
|
||||||
/// `objc_msg_send(obj, sel_<fieldName>)`. M2.2 — getter side.
|
/// `objc_msg_send(obj, sel_<fieldName>)`. M2.2 — getter side.
|
||||||
/// The setter side lives in the assignment-statement lowering.
|
/// The setter side lives in the assignment-statement lowering.
|
||||||
@@ -12082,39 +12200,27 @@ pub const Lowering = struct {
|
|||||||
const entry = self.builder.appendBlock(entry_name, &.{});
|
const entry = self.builder.appendBlock(entry_name, &.{});
|
||||||
self.builder.switchToBlock(entry);
|
self.builder.switchToBlock(entry);
|
||||||
|
|
||||||
// (1) Load ivar handle from per-class global.
|
// Pass the Obj-C receiver pointer through to the sx body as
|
||||||
const ivar_global_name = std.fmt.allocPrint(self.alloc, "__{s}_state_ivar", .{fcd.name}) catch return;
|
// `self`. The body's `self: *Self` type resolves to the
|
||||||
defer self.alloc.free(ivar_global_name);
|
// foreign-class stub (the opaque Obj-C type), matching Apple's
|
||||||
const ivar_global_id = self.lookupGlobalIdByName(ivar_global_name) orelse return;
|
// Obj-C semantics where `self` IS the object. `self.field`
|
||||||
const ivar_addr = self.builder.emit(.{ .global_addr = ivar_global_id }, ptr_void);
|
// access on a sx-defined class is rewritten by lowerFieldAccess
|
||||||
const ivar_handle = self.builder.load(ivar_addr, ptr_void);
|
// to go through `object_getIvar(self, __sx_state_ivar)` and
|
||||||
|
// a struct_gep on the state struct — see M1.2 A.3.
|
||||||
// (2) state = object_getIvar(obj, ivar_handle).
|
|
||||||
const get_ivar_fid = self.ensureCRuntimeDecl("object_getIvar", &.{ ptr_void, ptr_void }, ptr_void);
|
|
||||||
const obj_ref = Ref.fromIndex(0);
|
const obj_ref = Ref.fromIndex(0);
|
||||||
const get_ivar_args = self.alloc.alloc(Ref, 2) catch return;
|
|
||||||
get_ivar_args[0] = obj_ref;
|
|
||||||
get_ivar_args[1] = ivar_handle;
|
|
||||||
const state_ptr = self.builder.emit(.{ .call = .{
|
|
||||||
.callee = get_ivar_fid,
|
|
||||||
.args = get_ivar_args,
|
|
||||||
} }, ptr_void);
|
|
||||||
|
|
||||||
// (3) Call sx body `@<Cls>.<method>(default_ctx, state, ...user_args)`.
|
// Call sx body `@<Cls>.<method>(default_ctx, self, ...user_args)`.
|
||||||
const body_name = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fcd.name, md.name }) catch return;
|
const body_name = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fcd.name, md.name }) catch return;
|
||||||
defer self.alloc.free(body_name);
|
defer self.alloc.free(body_name);
|
||||||
const body_fid = self.resolveFuncByName(body_name) orelse return;
|
const body_fid = self.resolveFuncByName(body_name) orelse return;
|
||||||
|
|
||||||
// Locate __sx_default_context global. When implicit_ctx is off
|
|
||||||
// (no std.sx imported), the body has no __sx_ctx param either —
|
|
||||||
// skip the ctx prepend.
|
|
||||||
const ctx_ref: ?Ref = blk: {
|
const ctx_ref: ?Ref = blk: {
|
||||||
if (!self.implicit_ctx_enabled) break :blk null;
|
if (!self.implicit_ctx_enabled) break :blk null;
|
||||||
const dctx_gi = self.global_names.get("__sx_default_context") orelse break :blk null;
|
const dctx_gi = self.global_names.get("__sx_default_context") orelse break :blk null;
|
||||||
break :blk self.builder.emit(.{ .global_addr = dctx_gi.id }, ptr_void);
|
break :blk self.builder.emit(.{ .global_addr = dctx_gi.id }, ptr_void);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build arg list: [ctx?] + state + user_args.
|
// Build arg list: [ctx?] + self + user_args.
|
||||||
const num_user_args = params_slice.len - 2; // minus obj + _cmd
|
const num_user_args = params_slice.len - 2; // minus obj + _cmd
|
||||||
const num_call_args = (if (ctx_ref != null) @as(usize, 1) else 0) + 1 + num_user_args;
|
const num_call_args = (if (ctx_ref != null) @as(usize, 1) else 0) + 1 + num_user_args;
|
||||||
const call_args = self.alloc.alloc(Ref, num_call_args) catch return;
|
const call_args = self.alloc.alloc(Ref, num_call_args) catch return;
|
||||||
@@ -12123,9 +12229,8 @@ pub const Lowering = struct {
|
|||||||
call_args[idx] = c_ref;
|
call_args[idx] = c_ref;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
call_args[idx] = state_ptr;
|
call_args[idx] = obj_ref;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
// User args come from imp params slots 2..N.
|
|
||||||
var ip: usize = 2;
|
var ip: usize = 2;
|
||||||
while (ip < params_slice.len) : (ip += 1) {
|
while (ip < params_slice.len) : (ip += 1) {
|
||||||
call_args[idx] = Ref.fromIndex(@intCast(ip));
|
call_args[idx] = Ref.fromIndex(@intCast(ip));
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
@OBJC_METH_VAR_TYPE_.21 = private unnamed_addr constant [4 x i8] c"v@:\00"
|
@OBJC_METH_VAR_TYPE_.21 = private unnamed_addr constant [4 x i8] c"v@:\00"
|
||||||
@OBJC_METH_VAR_NAME_.22 = private unnamed_addr constant [6 x i8] c"alloc\00"
|
@OBJC_METH_VAR_NAME_.22 = private unnamed_addr constant [6 x i8] c"alloc\00"
|
||||||
@OBJC_METH_VAR_TYPE_.23 = private unnamed_addr constant [4 x i8] c"@@:\00"
|
@OBJC_METH_VAR_TYPE_.23 = private unnamed_addr constant [4 x i8] c"@@:\00"
|
||||||
@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__sx_objc_defined_class_init, ptr null }]
|
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare void @out(ptr) #0
|
declare void @out(ptr) #0
|
||||||
@@ -739,7 +738,9 @@ entry:
|
|||||||
%alloca = alloca ptr, align 8
|
%alloca = alloca ptr, align 8
|
||||||
store ptr %1, ptr %alloca, align 8
|
store ptr %1, ptr %alloca, align 8
|
||||||
%load = load ptr, ptr %alloca, align 8
|
%load = load ptr, ptr %alloca, align 8
|
||||||
%gep = getelementptr inbounds { i32 }, ptr %load, i32 0, i32 0
|
%loadN = load ptr, ptr @__SxFoo_state_ivar, align 8
|
||||||
|
%call = call ptr @object_getIvar(ptr %load, ptr %loadN)
|
||||||
|
%gep = getelementptr inbounds { i32 }, ptr %call, i32 0, i32 0
|
||||||
%loadN = load i32, ptr %gep, align 4
|
%loadN = load i32, ptr %gep, align 4
|
||||||
%add = add i32 %loadN, 1
|
%add = add i32 %loadN, 1
|
||||||
store i32 %add, ptr %gep, align 4
|
store i32 %add, ptr %gep, align 4
|
||||||
@@ -792,6 +793,9 @@ entry:
|
|||||||
ret { ptr, i64 } %call
|
ret { ptr, i64 } %call
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Function Attrs: nounwind
|
||||||
|
declare ptr @object_getIvar(ptr, ptr) #0
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define ptr @__SxFoo_alloc_imp(ptr %0, ptr %1) #0 {
|
define ptr @__SxFoo_alloc_imp(ptr %0, ptr %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
@@ -827,9 +831,6 @@ entry:
|
|||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
|
||||||
declare ptr @object_getIvar(ptr, ptr) #0
|
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
declare ptr @sel_registerName(ptr) #0
|
declare ptr @sel_registerName(ptr) #0
|
||||||
|
|
||||||
@@ -839,9 +840,7 @@ declare void @objc_msgSendSuper2(ptr, ptr) #0
|
|||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define void @__SxFoo_bump_imp(ptr %0, ptr %1) #0 {
|
define void @__SxFoo_bump_imp(ptr %0, ptr %1) #0 {
|
||||||
entry:
|
entry:
|
||||||
%load = load ptr, ptr @__SxFoo_state_ivar, align 8
|
call void @SxFoo.bump(ptr @__sx_default_context, ptr %0)
|
||||||
%call = call ptr @object_getIvar(ptr %0, ptr %load)
|
|
||||||
call void @SxFoo.bump(ptr @__sx_default_context, ptr %call)
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user