diff --git a/arc.m b/arc.m index 24f8b7c..8875bcc 100644 --- a/arc.m +++ b/arc.m @@ -530,17 +530,19 @@ id objc_storeStrong(id *addr, id value) // Weak references //////////////////////////////////////////////////////////////////////////////// +static int weakref_class; + typedef struct objc_weak_ref { + void *isa; id obj; - id *ref[4]; - struct objc_weak_ref *next; + size_t weak_count; } WeakRef; -static int weak_ref_compare(const id obj, const WeakRef weak_ref) +static int weak_ref_compare(const id obj, const WeakRef *weak_ref) { - return obj == weak_ref.obj; + return obj == weak_ref->obj; } static uint32_t ptr_hash(const void *ptr) @@ -549,23 +551,14 @@ static uint32_t ptr_hash(const void *ptr) // always be 0, which is not so useful for a hash value return ((uintptr_t)ptr >> 4) | ((uintptr_t)ptr << ((sizeof(id) * 8) - 4)); } -static int weak_ref_hash(const WeakRef weak_ref) -{ - return ptr_hash(weak_ref.obj); -} -static int weak_ref_is_null(const WeakRef weak_ref) +static int weak_ref_hash(const WeakRef *weak_ref) { - return weak_ref.obj == NULL; + return ptr_hash(weak_ref->obj); } -const static WeakRef NullWeakRef; #define MAP_TABLE_NAME weak_ref #define MAP_TABLE_COMPARE_FUNCTION weak_ref_compare #define MAP_TABLE_HASH_KEY ptr_hash #define MAP_TABLE_HASH_VALUE weak_ref_hash -#define MAP_TABLE_VALUE_TYPE struct objc_weak_ref -#define MAP_TABLE_VALUE_NULL weak_ref_is_null -#define MAP_TABLE_VALUE_PLACEHOLDER NullWeakRef -#define MAP_TABLE_ACCESS_BY_REFERENCE 1 #define MAP_TABLE_SINGLE_THREAD 1 #define MAP_TABLE_NO_LOCK 1 @@ -583,13 +576,56 @@ PRIVATE void init_arc(void) #endif } +/** + * Load from a weak pointer and return whether this really was a weak + * reference or a strong (not deallocatable) object in a weak pointer. The + * object will be stored in `obj` and the weak reference in `ref`, if one + * exists. + */ +__attribute__((always_inline)) +static BOOL loadWeakPointer(id *addr, id *obj, WeakRef **ref) +{ + id oldObj = *addr; + if (oldObj == nil) + { + *ref = NULL; + *obj = nil; + return NO; + } + if (classForObject(oldObj) == (Class)&weakref_class) + { + *ref = (WeakRef*)oldObj; + *obj = (*ref)->obj; + return YES; + } + *ref = NULL; + *obj = oldObj; + return NO; +} + +__attribute__((always_inline)) +static inline void weakRefRelease(WeakRef *ref) +{ + ref->weak_count--; + if (ref->weak_count == 0) + { + free(ref); + } +} + void* block_load_weak(void *block); id objc_storeWeak(id *addr, id obj) { LOCK_FOR_SCOPE(&weakRefLock); - id old = *addr; - + WeakRef *oldRef; + id old; + loadWeakPointer(addr, &old, &oldRef); + // If the old and new values are the same, then we don't need to do anything. + if (old == obj) + { + return obj; + } BOOL isGlobalObject = (obj == nil) || isSmallObject(obj); Class cls = Nil; if (!isGlobalObject) @@ -640,23 +676,13 @@ id objc_storeWeak(id *addr, id obj) } while (newVal != refCountVal); } } - if (nil != old) + // If we old ref exists, decrement its reference count. This may also + // delete the weak reference control block. + if (oldRef != NULL) { - WeakRef *oldRef = weak_ref_table_get(weakRefs, old); - while (NULL != oldRef) - { - for (int i=0 ; i<4 ; i++) - { - if (oldRef->ref[i] == addr) - { - oldRef->ref[i] = 0; - oldRef = 0; - break; - } - } - oldRef = (oldRef == NULL) ? NULL : oldRef->next; - } + weakRefRelease(oldRef); } + // If we're storing nil, then just write a null pointer. if (nil == obj) { *addr = obj; @@ -665,82 +691,30 @@ id objc_storeWeak(id *addr, id obj) if (isGlobalObject) { // If this is a global object, it's never deallocated, so secretly make - // this a strong reference + // this a strong reference. *addr = obj; return obj; } - if (&_NSConcreteMallocBlock == cls) - { - obj = block_load_weak(obj); - } - else if (objc_test_class_flag(cls, objc_class_flag_fast_arc)) - { - uintptr_t *refCount = ((uintptr_t*)obj) - 1; - if ((long)((__sync_fetch_and_add(refCount, 0) & refcount_mask)) < 0) - { - return nil; - } - } - else - { - obj = _objc_weak_load(obj); - } if (nil != obj) { WeakRef *ref = weak_ref_table_get(weakRefs, obj); - while (NULL != ref) + if (ref == NULL) { - for (int i=0 ; i<4 ; i++) - { - if (0 == ref->ref[i]) - { - ref->ref[i] = addr; - *addr = obj; - return obj; - } - } - if (ref->next == NULL) - { - break; - } - ref = ref->next; - } - if (NULL != ref) - { - ref->next = calloc(sizeof(WeakRef), 1); - ref->next->ref[0] = addr; + ref = calloc(1, sizeof(WeakRef)); + ref->isa = (Class)&weakref_class; + ref->obj = obj; + ref->weak_count = 1; + weak_ref_insert(weakRefs, ref); } else { - WeakRef newRef = {0}; - newRef.obj = obj; - newRef.ref[0] = addr; - weak_ref_insert(weakRefs, newRef); + ref->weak_count++; } + *addr = (id)ref; } - *addr = obj; return obj; } -static void zeroRefs(WeakRef *ref, BOOL shouldFree) -{ - if (NULL != ref->next) - { - zeroRefs(ref->next, YES); - } - for (int i=0 ; i<4 ; i++) - { - if (0 != ref->ref[i]) - { - *ref->ref[i] = 0; - } - } - if (shouldFree) - { - free(ref); - } -} - BOOL objc_delete_weak_refs(id obj) { LOCK_FOR_SCOPE(&weakRefLock); @@ -759,8 +733,16 @@ BOOL objc_delete_weak_refs(id obj) WeakRef *oldRef = weak_ref_table_get(weakRefs, obj); if (0 != oldRef) { - zeroRefs(oldRef, NO); + // Zero the object pointer. This prevents any other weak + // accesses from loading from this. + oldRef->obj = nil; + // The address of obj is likely to be reused, so remove it from + // the table so that we don't accidentally alias weak + // references weak_ref_remove(weakRefs, obj); + // If the weak reference count is zero, then we should have + // already removed this. + assert(oldRef->weak_count > 0); } return YES; } @@ -768,18 +750,21 @@ BOOL objc_delete_weak_refs(id obj) id objc_loadWeakRetained(id* addr) { LOCK_FOR_SCOPE(&weakRefLock); - // *addr can only be zeroed by another thread if it holds the weakRefLock. - // It is possible for another thread to zero the reference count here, but - // it will then acquire the weak ref lock prior to zeroing the weak - // references and deallocating the object. If this thread has incremented - // the reference count, then it will skip deallocating. - id obj = *addr; - if (nil == obj) { return nil; } - // Small objects don't need reference count modification - if (isSmallObject(obj)) + id obj; + WeakRef *ref; + // If this is really a strong reference (nil, or an non-deallocatable + // object), just return it. + if (!loadWeakPointer(addr, &obj, &ref)) { return obj; } + // The object cannot be deallocated while we hold the lock (release + // will acquire the lock before attempting to deallocate) + if (obj == nil) + { + weakRefRelease(ref); + return nil; + } Class cls = classForObject(obj); if (&_NSConcreteMallocBlock == cls) { @@ -808,20 +793,10 @@ void objc_moveWeak(id *dest, id *src) // the object can't be deallocated, so we just move the value and update // the weak reference table entry to indicate the new address. LOCK_FOR_SCOPE(&weakRefLock); + WeakRef *oldRef = weak_ref_table_get(weakRefs, *dest); *dest = *src; *src = nil; - WeakRef *oldRef = weak_ref_table_get(weakRefs, *dest); - while (NULL != oldRef) - { - for (int i=0 ; i<4 ; i++) - { - if (oldRef->ref[i] == src) - { - oldRef->ref[i] = dest; - return; - } - } - } + weakRefRelease(oldRef); } void objc_destroyWeak(id* obj)