ffi M4.B getter: weak property reads through objc_loadWeakRetained
emitObjcDefinedPropertyGetter dispatches on objcPropertyKind. The strong/copy/assign paths keep their bare load. The weak path: retained = objc_loadWeakRetained(field_addr) autoreleased = objc_autorelease(retained) return autoreleased `objc_loadWeakRetained` does the race-safe upgrade via libobjc's side-table: if the target has deinitialized (or is mid-dealloc on another thread), returns null; otherwise returns the target with refcount bumped (+1 retained, transferred to caller). `objc_autorelease` drops the +1 into the current pool so the caller doesn't need to manually balance — matches Apple's auto-nil weak-getter contract. The bare-load weak path (still in place pre-M4.B-getter) worked for the single-threaded test scenario because the runtime nils the slot before the load happens. The load-retained version covers the multi-threaded "between load and use, target deinit's" race that silent bare-load can't. 189/189 example tests pass; chess on iOS-sim green.
This commit is contained in:
@@ -12256,8 +12256,39 @@ pub const Lowering = struct {
|
||||
get_args[1] = ivar_handle;
|
||||
const state_ptr = self.builder.emit(.{ .call = .{ .callee = get_ivar_fid, .args = get_args } }, ptr_void);
|
||||
|
||||
// GEP to the field, load.
|
||||
const field_addr = self.builder.emit(.{ .struct_gep = .{ .base = state_ptr, .field_index = fidx, .base_type = state_ty } }, ptr_void);
|
||||
|
||||
// M4.B getter — weak fields go through objc_loadWeakRetained +
|
||||
// objc_autorelease for race-safe reads. The bare-load path
|
||||
// (strong/copy/assign) is the common case and reads the slot
|
||||
// directly.
|
||||
const kind = self.objcPropertyKind(field);
|
||||
if (kind == .weak) {
|
||||
self.ensureArcRuntimeDecls();
|
||||
const load_weak_fid = self.ensureCRuntimeDecl("objc_loadWeakRetained", &.{ptr_void}, ptr_void);
|
||||
const autorelease_fid = self.ensureCRuntimeDecl("objc_autorelease", &.{ptr_void}, ptr_void);
|
||||
|
||||
// retained = objc_loadWeakRetained(field_addr)
|
||||
// - atomic upgrade-to-strong via libobjc's side-table; if the
|
||||
// target deinitialised, returns null. The caller gets a
|
||||
// +1 retained reference (or null).
|
||||
const load_args = self.alloc.alloc(Ref, 1) catch return;
|
||||
load_args[0] = field_addr;
|
||||
const retained = self.builder.emit(.{ .call = .{ .callee = load_weak_fid, .args = load_args } }, ptr_void);
|
||||
|
||||
// autoreleased = objc_autorelease(retained)
|
||||
// - drops it into the current pool so the caller doesn't need
|
||||
// to manually release. Returns the same pointer (typed).
|
||||
const ar_args = self.alloc.alloc(Ref, 1) catch return;
|
||||
ar_args[0] = retained;
|
||||
const autoreleased = self.builder.emit(.{ .call = .{ .callee = autorelease_fid, .args = ar_args } }, ptr_void);
|
||||
|
||||
self.builder.ret(autoreleased, field_ty);
|
||||
self.builder.finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
// strong / copy / assign — bare load.
|
||||
const val = self.builder.load(field_addr, field_ty);
|
||||
self.builder.ret(val, field_ty);
|
||||
self.builder.finalize();
|
||||
|
||||
Reference in New Issue
Block a user