libs-base/Source/ObjectiveC2/weak.m

452 lines
9.8 KiB
Mathematica
Raw Permalink Normal View History

/* Emulation of ARC runtime support for weak references based on the gnustep
* runtime implementation.
*/
#import "common.h"
#import "Foundation/Foundation.h"
#import "../GSPrivate.h"
#import "../GSPThread.h"
static Class persistentClasses[1];
static int persistentClassCount = sizeof(persistentClasses)/sizeof(Class);
/* This function needs to identify objects which should NOT be handled by
* weak references.
* Nil is a special case which can not be stored as a weak reference because
* it indicates the absence of an object etc.
* Persistent objects do not need any sort of weak (or strong) reference and
* if they are immutable then trying to mark them as referenced would crash.
*/
__attribute__((always_inline))
static inline BOOL
isPersistentObject(id obj)
{
Class c;
int i;
if (nil == obj)
{
return YES;
}
/* If the alignment of the object does not match that needed for a
* pointer (to the class of the object) then the object must be a
* special one of some sort and we assume it's persistent.
*/
#if GS_SIZEOF_VOIDP == 8
if ((intptr_t)obj & 15)
{
return YES;
}
#else
if ((intptr_t)obj & 7)
{
return YES;
}
#endif
c = object_getClass(obj);
if (class_isMetaClass(c))
{
return YES; // obj was a class rather than an instance
}
for (i = 0; i < persistentClassCount; i++)
{
if (persistentClasses[i] == c)
{
return YES; // the object is a persistent instance
}
}
return NO;
}
static int WeakRefClass = 0;
#define GSI_MAP_NODE_CLASS (&WeakRefClass)
#define GSI_MAP_CLEAR_KEY(M, X) ((*X).obj = nil)
#define GSI_MAP_HASH(M, X) ((NSUInteger)(X.obj) >> 2)
#define GSI_MAP_EQUAL(M, X, Y) (X.obj == Y.obj)
#define GSI_MAP_RETAIN_KEY(M, X)
#define GSI_MAP_RELEASE_KEY(M, X)
#define GSI_MAP_RETAIN_VAL(M, X)
#define GSI_MAP_RELEASE_VAL(M, X)
#define GSI_MAP_KTYPES GSUNION_OBJ
#define GSI_MAP_VTYPES GSUNION_NSINT
#include "GNUstepBase/GSIMap.h"
typedef GSIMapNode_t WeakRef;
static gs_mutex_t weakLock = GS_MUTEX_INIT_STATIC;
/* The weakRefs table contains weak references (nodes) for weak references
* to any active objects.
*/
static GSIMapTable_t weakRefs = { 0 };
/* The deallocated list contains the weak references (nodes) for objects
* which have already been deallocated (so the references are now to nil).
*/
static WeakRef *deallocated = NULL;
/* This must be called on startup before any weak references are taken.
*/
void
GSWeakInit()
{
GS_MUTEX_LOCK(weakLock);
if (0 == weakRefs.increment)
{
GSIMapInitWithZoneAndCapacity(
&weakRefs, NSDefaultMallocZone(), 1024);
persistentClasses[0] = [NXConstantString class];
}
GS_MUTEX_UNLOCK(weakLock);
}
/* 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.
*/
inline static BOOL
loadWeakPointer(id *addr, id *obj, WeakRef **ref)
{
id oldObj = *addr;
if (nil == oldObj)
{
*ref = NULL;
*obj = nil;
return NO;
}
if (*(void**)oldObj == (void*)&WeakRefClass)
{
*ref = (WeakRef*)oldObj;
*obj = (*ref)->key.obj;
return YES;
}
*ref = NULL;
*obj = oldObj;
return NO;
}
__attribute__((always_inline))
static inline BOOL
weakRefRelease(WeakRef *ref)
{
ref->value.nsi--;
if (ref->value.nsi == 0)
{
if (nil == ref->key.obj)
{
/* The object was already deallocated so we must remove this
* reference from the deallocated list.
*/
if (deallocated == ref)
{
deallocated = ref->nextInBucket;
}
else
{
WeakRef *tmp = deallocated;
while (tmp->nextInBucket != 0)
{
if (tmp->nextInBucket == ref)
{
tmp->nextInBucket = ref->nextInBucket;
break;
}
tmp = tmp->nextInBucket;
}
}
ref->nextInBucket = weakRefs.freeNodes;
weakRefs.freeNodes = ref;
}
else
{
GSIMapBucket bucket = GSIMapBucketForKey(&weakRefs, ref->key);
GSIMapRemoveNodeFromMap(&weakRefs, bucket, ref);
GSIMapFreeNode(&weakRefs, ref);
}
return YES;
}
return NO;
}
/* We should record the fact that the object has weak references (unless
* it is a persistent one).
* Return YES if the object is persistent and should not have weak references,
* NO otherwise.
*/
static BOOL
setObjectHasWeakRefs(id obj)
{
BOOL isPersistent = isPersistentObject(obj);
if (NO == isPersistent)
{
/* FIXME ... for performance we should mark the object as having
* weak references and we should check that in objc_delete_weak_refs()
*/
}
return isPersistent;
}
static WeakRef *
incrementWeakRefCount(id obj)
{
GSIMapKey key;
GSIMapBucket bucket;
WeakRef *ref;
/* Mark the instance as having had a weak reference at some point.
* This information is used when the instance is deallocated.
*/
GSPrivateMarkedWeak(obj, YES);
key.obj = obj;
bucket = GSIMapBucketForKey(&weakRefs, key);
ref = GSIMapNodeForKeyInBucket(&weakRefs, bucket, key);
if (NULL == ref)
{
ref = GSIMapGetNode(&weakRefs);
ref->key.obj = obj;
ref->value.nsi = 1;
GSIMapAddNodeToBucket(bucket, ref);
weakRefs.nodeCount++;
return ref;
}
ref->value.nsi++;
return ref;
}
id
objc_storeWeak(id *addr, id obj)
{
WeakRef *oldRef;
id old;
BOOL isGlobalObject;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(addr, &old, &oldRef);
/* If the old and new values are the same (and we are not setting a nil
* value to destroy an existing weak reference), then we don't need to
* do anything.
*/
if ((obj != nil || oldRef == NULL) && old == obj)
{
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
isGlobalObject = setObjectHasWeakRefs(obj);
/* If we old ref exists, decrement its reference count. This may also
* delete the weak reference from the map.
*/
if (oldRef != NULL)
{
weakRefRelease(oldRef);
}
/* If we're storing nil, then just write a null pointer.
*/
if (nil == obj)
{
*addr = obj;
}
else if (isGlobalObject)
{
/* If this is a global object, it's never deallocated,
* so we don't make this a weak reference.
*/
*addr = obj;
}
else if ([obj retainCount] == 0)
{
/* The object is being deallocated ... we must store nil.
*/
*addr = obj = nil;
}
else
{
*addr = (id)incrementWeakRefCount(obj);
}
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
/* Function called when objects are deallocated
*/
BOOL
objc_delete_weak_refs(id obj)
{
GSIMapKey key;
GSIMapBucket bucket;
WeakRef *ref;
/* For performance we should have marked the object as having
* weak references and we check that in order to avoid the cost
* of the map table lookup when it's not needed.
*/
if (NO == GSPrivateMarkedWeak(obj, NO))
{
return NO;
}
key.obj = obj;
GS_MUTEX_LOCK(weakLock);
bucket = GSIMapBucketForKey(&weakRefs, key);
ref = GSIMapNodeForKeyInBucket(&weakRefs, bucket, key);
if (ref)
{
GSIMapRemoveNodeFromBucket(bucket, ref);
ref->key.obj = nil;
weakRefs.nodeCount--;
/* The object is deallocated but there are still weak references
* to it so we put the weak reference node in the deallocated list.
*/
ref->nextInBucket = deallocated;
deallocated = ref;
}
GS_MUTEX_UNLOCK(weakLock);
return YES;
}
id
objc_loadWeakRetained(id *addr)
{
id obj;
WeakRef *ref;
GS_MUTEX_LOCK(weakLock);
/* If this is not actually a weak pointer, return the object directly.
*/
if (!loadWeakPointer(addr, &obj, &ref))
{
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
if (nil == obj)
{
/* The object has been destroed so we should remove the weak
* reference to it.
*/
if (ref != NULL)
{
weakRefRelease(ref);
*addr = nil;
}
GS_MUTEX_UNLOCK(weakLock);
return nil;
}
obj = [obj retain];
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
id
objc_loadWeak(id *object)
{
return [objc_loadWeakRetained(object) autorelease];
}
void
objc_copyWeak(id *dest, id *src)
{
/* Don't retain or release.
* 'src' is a valid pointer to a __weak pointer or nil.
* 'dest' is a valid pointer to uninitialised memory.
* After this operation, 'dest' should contain whatever 'src' contained.
*/
id obj;
WeakRef *srcRef;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(src, &obj, &srcRef);
*dest = *src;
if (srcRef)
{
srcRef->value.nsi++;
}
GS_MUTEX_UNLOCK(weakLock);
}
void
objc_moveWeak(id *dest, id *src)
{
/* Don't retain or release.
* 'dest' is a valid pointer to uninitialized memory.
* 'src' is a valid pointer to a __weak pointer.
* This operation moves from *src to *dest and must be atomic with respect
* to other stores to *src via 'objc_storeWeak'.
*/
GS_MUTEX_LOCK(weakLock);
*dest = *src;
*src = nil;
GS_MUTEX_UNLOCK(weakLock);
}
void
objc_destroyWeak(id *obj)
{
WeakRef *oldRef;
id old;
GS_MUTEX_LOCK(weakLock);
loadWeakPointer(obj, &old, &oldRef);
/* If the old ref exists, decrement its reference count.
* This may also remove the weak reference from the map.
*/
if (oldRef != NULL)
{
weakRefRelease(oldRef);
}
GS_MUTEX_UNLOCK(weakLock);
}
id
objc_initWeak(id *addr, id obj)
{
BOOL isGlobalObject;
if (nil == obj)
{
*addr = nil;
return nil;
}
GS_MUTEX_LOCK(weakLock);
isGlobalObject = setObjectHasWeakRefs(obj);
if (isGlobalObject)
{
*addr = obj;
GS_MUTEX_UNLOCK(weakLock);
return obj;
}
if ([obj retainCount] == 0)
{
*addr = nil;
GS_MUTEX_UNLOCK(weakLock);
return nil;
}
if (nil != obj)
{
*(WeakRef**)addr = incrementWeakRefCount(obj);
}
GS_MUTEX_UNLOCK(weakLock);
return obj;
}