ffi M4.B dealloc: release strong/copy property ivars + destroyWeak weak
emitObjcDefinedClassDeallocImp now walks the class's #property fields
BEFORE freeing the state struct. For each:
- assign → no-op (primitives, no ARC traffic).
- strong → val = load field; objc_release(val).
- copy → same as strong (the stored value is a +1 retained copy
produced by the setter's [val copy]; we release it here).
- weak → objc_destroyWeak(&field) — unregisters the slot from
libobjc's side-table so the runtime stops tracking it.
Order matters: property releases happen BEFORE freeing the state
struct (which would invalidate the pointers we need to read), which
happens BEFORE [super dealloc] (which eventually frees the Obj-C
instance's own memory). The full sequence is now:
%state = object_getIvar(self, __sx_state_ivar)
// M4.B (this commit):
for each strong/copy property P:
val = load struct_gep(state, P.idx); objc_release(val)
for each weak property P:
objc_destroyWeak(struct_gep(state, P.idx))
// M4.0c (already shipped):
allocator = load struct_gep(state, 0)
allocator.dealloc(state)
object_setIvar(self, ivar, null)
// M1.2 A.6:
[super dealloc] // → objc_msgSendSuper2
ffi-objc-arc-02-strong-property now passes: child held by parent's
strong property gets released when parent deallocates, refcount → 0,
child deallocates, both states freed via tracker. Balanced 2/2.
189/189 example tests pass; chess on iOS-sim green. M4 complete.
This commit is contained in:
@@ -12904,7 +12904,69 @@ pub const Lowering = struct {
|
||||
get_args[1] = ivar_handle;
|
||||
const state = self.builder.emit(.{ .call = .{ .callee = get_ivar_fid, .args = get_args } }, ptr_void);
|
||||
|
||||
// (2) Free state through the captured allocator (M4.0a + M4.0b):
|
||||
// (2) M4.B dealloc — release strong/copy property ivars and
|
||||
// destroyWeak weak property ivars BEFORE freeing the state struct
|
||||
// (which would invalidate the pointers we need to read). Property
|
||||
// metadata is re-derived from `fcd.members`; the state struct is
|
||||
// already interned via objcDefinedStateStructType.
|
||||
const state_struct_ty = self.objcDefinedStateStructType(fcd);
|
||||
const state_info_check = self.module.types.get(state_struct_ty);
|
||||
if (state_info_check == .@"struct") {
|
||||
const state_fields = state_info_check.@"struct".fields;
|
||||
for (fcd.members) |m| switch (m) {
|
||||
.field => |f| {
|
||||
if (!f.is_property) continue;
|
||||
// Find the field index in the state struct (by name —
|
||||
// M4.0a's prepended __sx_allocator shifted user fields).
|
||||
const field_name_id = self.module.types.internString(f.name);
|
||||
var pfidx: ?u32 = null;
|
||||
for (state_fields, 0..) |sf, i| {
|
||||
if (sf.name == field_name_id) {
|
||||
pfidx = @intCast(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const fidx = pfidx orelse continue;
|
||||
const field_ty = self.resolveType(f.field_type);
|
||||
const kind = self.objcPropertyKind(f);
|
||||
|
||||
switch (kind) {
|
||||
.assign => {}, // no ARC ops
|
||||
.strong, .copy => {
|
||||
// val = load field; objc_release(val) — release(NULL) is a no-op.
|
||||
self.ensureArcRuntimeDecls();
|
||||
const release_fid = self.ensureCRuntimeDecl("objc_release", &.{ptr_void}, .void);
|
||||
const field_addr = self.builder.emit(.{ .struct_gep = .{
|
||||
.base = state,
|
||||
.field_index = fidx,
|
||||
.base_type = state_struct_ty,
|
||||
} }, ptr_void);
|
||||
const val = self.builder.load(field_addr, field_ty);
|
||||
const args = self.alloc.alloc(Ref, 1) catch continue;
|
||||
args[0] = val;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = release_fid, .args = args } }, .void);
|
||||
},
|
||||
.weak => {
|
||||
// objc_destroyWeak(&field) — unregisters the slot
|
||||
// from libobjc's side-table.
|
||||
self.ensureArcRuntimeDecls();
|
||||
const destroy_weak_fid = self.ensureCRuntimeDecl("objc_destroyWeak", &.{ptr_void}, .void);
|
||||
const field_addr = self.builder.emit(.{ .struct_gep = .{
|
||||
.base = state,
|
||||
.field_index = fidx,
|
||||
.base_type = state_struct_ty,
|
||||
} }, ptr_void);
|
||||
const args = self.alloc.alloc(Ref, 1) catch continue;
|
||||
args[0] = field_addr;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = destroy_weak_fid, .args = args } }, .void);
|
||||
},
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
// (3) Free state through the captured allocator (M4.0a + M4.0b):
|
||||
// allocator = load struct_gep(state, 0) ← __sx_allocator field
|
||||
// allocator.dealloc(state) ← inline-protocol fn-ptr at field 2
|
||||
// Compare to the old `free(state)` — that ignored the per-instance
|
||||
@@ -12925,7 +12987,6 @@ pub const Lowering = struct {
|
||||
}
|
||||
const allocator_ty = ctx_info.@"struct".fields[0].ty;
|
||||
|
||||
const state_struct_ty = self.objcDefinedStateStructType(fcd);
|
||||
const state_alloc_addr = self.builder.emit(.{ .struct_gep = .{
|
||||
.base = state,
|
||||
.field_index = 0,
|
||||
|
||||
Reference in New Issue
Block a user