Add support (and test) for weak keys and values in NSMapTable. This support should work in GC mode. It also works if the runtime supports ARC, even if the compiler does not use this support.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@33617 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
David Chisnall 2011-07-23 16:16:01 +00:00
parent 3e3d12b661
commit bc37adfb0c
4 changed files with 123 additions and 78 deletions

View file

@ -109,7 +109,7 @@ extern "C" {
*
* GSI_MAP_ZEROED()
* Define this macro to check whether a map uses keys which may
* be zeroed weak pointers. This is only used when GC is enabled.
* be zeroed weak pointers.
*/
#ifndef GSI_MAP_HAS_VALUE
@ -142,13 +142,21 @@ extern "C" {
#define GSI_MAP_ZEROED(M) 0
#endif
#ifndef GSI_MAP_READ_KEY
# define GSI_MAP_READ_KEY(x) (*(x))
# define GSI_MAP_READ_KEY(M, x) (*(x))
#endif
#ifndef GSI_MAP_READ_VALUE
# define GSI_MAP_READ_VALUE(M, x) (*(x))
#endif
#ifndef GSI_MAP_WRITE_KEY
# define GSI_MAP_WRITE_KEY(addr, obj) (*(addr) = obj)
# define GSI_MAP_WRITE_KEY(M, addr, obj) (*(addr) = obj)
#endif
#ifndef GSI_MAP_WRITE_VAL
# define GSI_MAP_WRITE_VAL(addr, obj) (*(addr) = obj)
# define GSI_MAP_WRITE_VAL(M, addr, obj) (*(addr) = obj)
#endif
#if GSI_MAP_HAS_VALUE
#define GSI_MAP_NODE_IS_EMPTY(M, node) (((GSI_MAP_READ_VALUE(M, &node->key).addr) == 0) || ((GSI_MAP_READ_VALUE(M, &node->value).addr == 0)))
#else
#define GSI_MAP_NODE_IS_EMPTY(M, node) (((GSI_MAP_READ_VALUE(M, &node->key).addr) == 0))
#endif
/*
@ -193,9 +201,9 @@ extern "C" {
#if (GSI_MAP_KTYPES) & GSUNION_OBJ
#define GSI_MAP_CLEAR_KEY(node) GSI_MAP_WRITE_KEY(&node->key.obj, nil)
#define GSI_MAP_CLEAR_KEY(node) GSI_MAP_WRITE_KEY(map, &node->key, (GSIMapKey)nil)
#elif (GSI_MAP_KTYPES) & GSUNION_PTR
#define GSI_MAP_CLEAR_KEY(node) GSI_MAP_WRITE_KEY(&node->key.ptr, 0)
#define GSI_MAP_CLEAR_KEY(node) GSI_MAP_WRITE_KEY(map, &node->key, (GSIMapKey)NULL)
#else
#define GSI_MAP_CLEAR_KEY(node)
#endif
@ -245,9 +253,9 @@ extern "C" {
#endif
#if (GSI_MAP_VTYPES) & GSUNION_OBJ
#define GSI_MAP_CLEAR_VAL(node) GSI_MAP_WRITE_VAL(&node->value.obj, nil)
#define GSI_MAP_CLEAR_VAL(node) GSI_MAP_WRITE_VAL(map, &node->value, (GSIMapVal)nil)
#elif (GSI_MAP_VTYPES) & GSUNION_PTR
#define GSI_MAP_CLEAR_VAL(node) GSI_MAP_WRITE_VAL(&node->value.ptr, 0)
#define GSI_MAP_CLEAR_VAL(node) GSI_MAP_WRITE_VAL(map, &node->value, (GSIMapVal)NULL)
#else
#define GSI_MAP_CLEAR_VAL(node)
#endif
@ -478,7 +486,6 @@ GSIMapRemangleBuckets(GSIMapTable map,
GSIMapBucket old_buckets, uintptr_t old_bucketCount,
GSIMapBucket new_buckets, uintptr_t new_bucketCount)
{
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while (old_bucketCount-- > 0)
@ -487,7 +494,7 @@ GSIMapRemangleBuckets(GSIMapTable map,
while ((node = old_buckets->firstNode) != 0)
{
if (node->key.addr == 0)
if (GSI_MAP_NODE_IS_EMPTY(map, node))
{
GSIMapRemoveNodeFromMap(map, old_buckets, node);
GSIMapFreeNode(map, node);
@ -506,7 +513,6 @@ GSIMapRemangleBuckets(GSIMapTable map,
}
return;
}
#endif
while (old_bucketCount-- > 0)
{
GSIMapNode node;
@ -592,14 +598,13 @@ GSIMapNodeForKeyInBucket(GSIMapTable map, GSIMapBucket bucket, GSIMapKey key)
{
GSIMapNode node = bucket->firstNode;
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while ((node != 0) && GSI_MAP_EQUAL(map, GSI_MAP_READ_KEY(&node->key), key) == NO)
while ((node != 0) && GSI_MAP_EQUAL(map, GSI_MAP_READ_KEY(map, &node->key), key) == NO)
{
GSIMapNode tmp = node->nextInBucket;
if (GSI_MAP_READ_KEY(&node->key).addr == 0)
if (GSI_MAP_NODE_IS_EMPTY(map, node))
{
GSIMapRemoveNodeFromMap(map, bucket, node);
GSIMapFreeNode(map, node);
@ -608,8 +613,7 @@ GSIMapNodeForKeyInBucket(GSIMapTable map, GSIMapBucket bucket, GSIMapKey key)
}
return node;
}
#endif
while ((node != 0) && GSI_MAP_EQUAL(map, GSI_MAP_READ_KEY(&node->key), key) == NO)
while ((node != 0) && GSI_MAP_EQUAL(map, GSI_MAP_READ_KEY(map, &node->key), key) == NO)
{
node = node->nextInBucket;
}
@ -640,13 +644,12 @@ GSIMapFirstNode(GSIMapTable map)
uintptr_t bucket = 0;
GSIMapNode node = 0;
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while (bucket < count)
{
node = map->buckets[bucket].firstNode;
while (node != 0 && GSI_MAP_READ_KEY(&node->key).addr == 0)
while (node != 0 && GSI_MAP_NODE_IS_EMPTY(map, node))
{
node = GSIMapRemoveAndFreeNode(map, bucket, node);
}
@ -658,7 +661,6 @@ GSIMapFirstNode(GSIMapTable map)
}
return node;
}
#endif
while (bucket < count)
{
node = map->buckets[bucket].firstNode;
@ -694,14 +696,13 @@ GSIMapNodeForSimpleKey(GSIMapTable map, GSIMapKey key)
}
bucket = map->buckets + ((unsigned)key.addr) % map->bucketCount;
node = bucket->firstNode;
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while ((node != 0) && GSI_MAP_READ_KEY(&node->key).addr != key.addr)
while ((node != 0) && GSI_MAP_READ_KEY(map, &node->key).addr != key.addr)
{
GSIMapNode tmp = node->nextInBucket;
if (GSI_MAP_READ_KEY(&node->key).addr == 0)
if (GSI_MAP_NODE_IS_EMPTY(map, node))
{
GSIMapRemoveNodeFromMap(map, bucket, node);
GSIMapFreeNode(map, node);
@ -710,8 +711,7 @@ GSIMapNodeForSimpleKey(GSIMapTable map, GSIMapKey key)
}
return node;
}
#endif
while ((node != 0) && GSI_MAP_READ_KEY(&node->key).addr != key.addr)
while ((node != 0) && GSI_MAP_READ_KEY(map, &node->key).addr != key.addr)
{
node = node->nextInBucket;
}
@ -820,14 +820,13 @@ GSIMapEnumeratorForMap(GSIMapTable map)
/*
* Locate next bucket and node to be returned.
*/
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while (enumerator.bucket < map->bucketCount)
{
GSIMapNode node = map->buckets[enumerator.bucket].firstNode;
while (node != 0 && GSI_MAP_READ_KEY(&node->key).addr == 0)
while (node != 0 && GSI_MAP_READ_KEY(map, &node->key).addr == 0)
{
node = GSIMapRemoveAndFreeNode(map, enumerator.bucket, node);
}
@ -838,7 +837,6 @@ GSIMapEnumeratorForMap(GSIMapTable map)
enumerator.bucket++;
}
}
#endif
while (enumerator.bucket < map->bucketCount)
{
enumerator.node = map->buckets[enumerator.bucket].firstNode;
@ -890,21 +888,20 @@ GSIMapEnumeratorNextNode(GSIMapEnumerator enumerator)
GSIMapNode node = ((_GSIE)enumerator)->node;
GSIMapTable map = ((_GSIE)enumerator)->map;
#if GS_WITH_GC
/* Find the frst available non-zeroed node.
*/
if (node != 0 && GSI_MAP_ZEROED(map) && GSI_MAP_READ_KEY(&node->key).addr == 0)
if (node != 0 && GSI_MAP_ZEROED(map) && GSI_MAP_READ_KEY(map, &node->key).addr == 0)
{
uintptr_t bucketCount = map->bucketCount;
uintptr_t bucket = ((_GSIE)enumerator)->bucket;
while (node != 0 && GSI_MAP_READ_KEY(&node->key).addr == 0)
while (node != 0 && GSI_MAP_READ_KEY(map, &node->key).addr == 0)
{
node = GSIMapRemoveAndFreeNode(map, bucket, node);
while (node == 0 && ++bucket < bucketCount)
{
node = (map->buckets[bucket]).firstNode;
while (node != 0 && GSI_MAP_READ_KEY(&node->key).addr == 0)
while (node != 0 && GSI_MAP_READ_KEY(map, &node->key).addr == 0)
{
node = GSIMapRemoveAndFreeNode(map, bucket, node);
}
@ -913,13 +910,11 @@ GSIMapEnumeratorNextNode(GSIMapEnumerator enumerator)
((_GSIE)enumerator)->node = node;
}
}
#endif
if (node != 0)
{
GSIMapNode next = node->nextInBucket;
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
uintptr_t bucket = ((_GSIE)enumerator)->bucket;
@ -929,14 +924,12 @@ GSIMapEnumeratorNextNode(GSIMapEnumerator enumerator)
next = GSIMapRemoveAndFreeNode(map, bucket, next);
}
}
#endif
if (next == 0)
{
uintptr_t bucketCount = map->bucketCount;
uintptr_t bucket = ((_GSIE)enumerator)->bucket;
#if GS_WITH_GC
if (GSI_MAP_ZEROED(map))
{
while (next == 0 && ++bucket < bucketCount)
@ -951,7 +944,6 @@ GSIMapEnumeratorNextNode(GSIMapEnumerator enumerator)
((_GSIE)enumerator)->node = next;
return node;
}
#endif
while (next == 0 && ++bucket < bucketCount)
{
next = (map->buckets[bucket]).firstNode;
@ -1011,7 +1003,7 @@ GSIMapCountByEnumeratingWithStateObjectsCount(GSIMapTable map,
* will only work with things that are id-sized, however, so don't
* try using it with non-object collections.
*/
stackbuf[i] = *(id*)(void*)&GSI_MAP_READ_KEY(&node->key).addr;
stackbuf[i] = (id)GSI_MAP_READ_KEY(map, &node->key).addr;
}
}
/* Store the important bits of the enumerator in the caller. */
@ -1039,8 +1031,8 @@ GSIMapAddPairNoRetain(GSIMapTable map, GSIMapKey key, GSIMapVal value)
}
}
map->freeNodes = node->nextInBucket;
GSI_MAP_WRITE_KEY(&node->key, key);
GSI_MAP_WRITE_VAL(&node->value, value);
GSI_MAP_WRITE_KEY(map, &node->key, key);
GSI_MAP_WRITE_VAL(map, &node->value, value);
node->nextInBucket = 0;
GSIMapRightSizeMap(map, map->nodeCount);
GSIMapAddNodeToMap(map, node);
@ -1062,9 +1054,9 @@ GSIMapAddPair(GSIMapTable map, GSIMapKey key, GSIMapVal value)
}
}
map->freeNodes = node->nextInBucket;
GSI_MAP_WRITE_KEY(&node->key, key);
GSI_MAP_WRITE_KEY(map, &node->key, key);
GSI_MAP_RETAIN_KEY(map, node->key);
GSI_MAP_WRITE_VAL(&node->value, value);
GSI_MAP_WRITE_VAL(map, &node->value, value);
GSI_MAP_RETAIN_VAL(map, node->value);
node->nextInBucket = 0;
GSIMapRightSizeMap(map, map->nodeCount);
@ -1087,7 +1079,7 @@ GSIMapAddKeyNoRetain(GSIMapTable map, GSIMapKey key)
}
}
map->freeNodes = node->nextInBucket;
GSI_MAP_WRITE_KEY(&node->key, node->key);
GSI_MAP_WRITE_KEY(map, &node->key, node->key);
node->nextInBucket = 0;
GSIMapRightSizeMap(map, map->nodeCount);
GSIMapAddNodeToMap(map, node);
@ -1109,7 +1101,7 @@ GSIMapAddKey(GSIMapTable map, GSIMapKey key)
}
}
map->freeNodes = node->nextInBucket;
GSI_MAP_WRITE_KEY(&node->key, key);
GSI_MAP_WRITE_KEY(map, &node->key, key);
GSI_MAP_RETAIN_KEY(map, node->key);
node->nextInBucket = 0;
GSIMapRightSizeMap(map, map->nodeCount);

View file

@ -97,10 +97,28 @@ typedef GSIMapNode_t *GSIMapNode;
#define GSI_MAP_RETAIN_VAL(M, X)\
(M->legacy ? M->cb.old.v.retain(M, X.ptr) \
: pointerFunctionsAcquire(&M->cb.pf.v, &X.ptr, X.ptr))
#define GSI_MAP_WRITE_KEY(M, addr, x) \
if (M->legacy) \
*(addr) = x;\
else\
pointerFunctionsAssign(&M->cb.pf.k, (void**)addr, (x).obj);
#define GSI_MAP_WRITE_VAL(M, addr, x) \
if (M->legacy) \
*(addr) = x;\
else\
pointerFunctionsAssign(&M->cb.pf.v, (void**)addr, (x).obj);
#define GSI_MAP_READ_KEY(M,addr) \
(M->legacy ? *(addr) :\
(typeof(*addr))pointerFunctionsRead(&M->cb.pf.k, (void**)addr))
#define GSI_MAP_READ_VALUE(M,addr) \
(M->legacy ? *(addr) :\
(typeof(*addr))pointerFunctionsRead(&M->cb.pf.v, (void**)addr))
#define GSI_MAP_ZEROED(M)\
(M->legacy ? 0 \
: ((M->cb.pf.k.options & NSPointerFunctionsZeroingWeakMemory) ? YES : NO))
#define GSI_MAP_ENUMERATOR NSMapEnumerator
#if GS_WITH_GC
@ -1183,6 +1201,10 @@ const NSMapTableValueCallBacks NSOwnedPointerMapValueCallBacks =
- (NSUInteger) count
{
if (!legacy && (cb.pf.k.options | cb.pf.v.options) & NSPointerFunctionsZeroingWeakMemory)
{
GSIMapCleanMap(self);
}
return (NSUInteger)nodeCount;
}

View file

@ -24,10 +24,38 @@
*/
#import "Foundation/NSPointerFunctions.h"
#if __OBJC_GC__
#include <objc/objc-auto.h>
#ifdef __GNUSTEP_RUNTIME__
# include <objc/capabilities.h>
#endif
// Define a weak read barrier macro for ARC or GC, depending on which one this
// target supports. If this target doesn't support zeroing weak references,
// then use an unsafe unretained access.
#if __OBJC_GC__
# include <objc/objc-auto.h>
# define WEAK_READ(x) objc_read_weak((id*)x)
# define WEAK_WRITE(addr, x) objc_assign_weak((id)x, (id*)addr)
# define STRONG_WRITE(addr, x) objc_assign_strongCast((id)x, (id*)addr)
# define STRONG_ACQUIRE(x) x
#elif defined(OBJC_CAP_ARC)
# include <objc/objc-arc.h>
# define WEAK_READ(x) objc_loadWeak((id*)x)
# define WEAK_WRITE(addr, x) objc_storeWeak((id*)addr, (id)x)
# define STRONG_WRITE(addr, x) objc_storeStrong((id*)addr, (id)x)
# define STRONG_ACQUIRE(x) objc_retain(x)
#else
# define WEAK_READ(x) (*x)
# if GS_WITH_GC
# define WEAK_WRITE(addr, x) GSAssignZeroingWeakPointer(addr, x)
# else
# define WEAK_WRITE(addr, x) (*(addr) = x)
# endif
# define STRONG_WRITE(addr, x) ASSIGN(*(addr), x)
# define STRONG_ACQUIRE(x) RETAIN(x)
#endif
/* Declare a structure type to copy pointer functions information
* around easily.
*/
@ -68,29 +96,13 @@ typedef struct
/* Acquire the pointer value to store for the specified item.
*/
static inline void
static inline void *
pointerFunctionsAcquire(PFInfo *PF, void **dst, void *src)
{
if (PF->acquireFunction != 0)
src = (*PF->acquireFunction)(src, PF->sizeFunction,
PF->options & NSPointerFunctionsCopyIn ? YES : NO);
#if __OBJC_GC__
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
{
objc_assign_weak((id)src, (id*)dst);
}
else
{
objc_assign_strongCast((id)src, (id*)dst);
}
#else
#if GS_WITH_GC
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
GSAssignZeroingWeakPointer(dst, src);
else
#endif
*dst = src;
#endif
return src;
}
/**
@ -99,12 +111,10 @@ pointerFunctionsAcquire(PFInfo *PF, void **dst, void *src)
*/
static inline void *pointerFunctionsRead(PFInfo *PF, void **addr)
{
#if __OBJC_GC__
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
{
return objc_read_weak((id*)addr);
return WEAK_READ((id*)addr);
}
#endif
return *addr;
}
@ -113,21 +123,18 @@ static inline void *pointerFunctionsRead(PFInfo *PF, void **addr)
*/
static inline void pointerFunctionsAssign(PFInfo *PF, void **addr, void *value)
{
#if __OBJC_GC__
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
{
objc_assign_weak(value, (id*)addr);
WEAK_WRITE(addr, value);
}
else if (PF->options & NSPointerFunctionsStrongMemory)
{
STRONG_WRITE(addr, value);
}
else
{
objc_assign_strongCast(value, (id*)addr);
*addr = value;
}
#elif GS_WITH_GC
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
GSAssignZeroingWeakPointer(addr, value);
#else
*addr = value;
#endif
}
/**
@ -179,6 +186,7 @@ pointerFunctionsEqual(PFInfo *PF, void *item1, void *item2)
static inline void
pointerFunctionsRelinquish(PFInfo *PF, void **itemptr)
{
if (PF->relinquishFunction != 0)
(*PF->relinquishFunction)(*itemptr, PF->sizeFunction);
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
@ -198,11 +206,9 @@ pointerFunctionsReplace(PFInfo *PF, void **dst, void *src)
PF->options & NSPointerFunctionsCopyIn ? YES : NO);
if (PF->relinquishFunction != 0)
(*PF->relinquishFunction)(*dst, PF->sizeFunction);
#if GS_WITH_GC
if (PF->options & NSPointerFunctionsZeroingWeakMemory)
GSAssignZeroingWeakPointer(dst, src);
WEAK_WRITE(dst, src);
else
#endif
*dst = src;
}
}

View file

@ -0,0 +1,25 @@
#import "ObjectTesting.h"
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSMapTable.h>
int main()
{
[NSAutoreleasePool new];
NSMapTable *map = [NSMapTable mapTableWithStrongToWeakObjects];
NSMapTable *map2 = [NSMapTable mapTableWithWeakToStrongObjects];
id obj = [NSObject new];
[map setObject: obj forKey: @"1"];
[map2 setObject: @"1" forKey: obj];
PASS(obj == [map objectForKey: @"1"], "Value stored in weak-value map");
PASS(nil != [map2 objectForKey: obj], "Value stored in weak-key map");
[obj release];
PASS(nil == [map objectForKey: @"1"], "Value removed from weak-value map");
NSEnumerator *enumerator = [map2 keyEnumerator];
NSUInteger count = 0;
while ([enumerator nextObject] != nil) { count++; }
PASS(count == 0, "Value removed from weak-key map");
PASS(0 == [map count], "Weak-value map reports correct count");
PASS(0 == [map2 count], "Weak-key map reports correct count");
return 0;
}