X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/7c0e6487d7b67b6bf6c632300ee4b74e8950b051..7af964d1562d70f51a8e9aca24215ac3d83d0624:/runtime/objc-references.mm diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm new file mode 100644 index 0000000..cada135 --- /dev/null +++ b/runtime/objc-references.mm @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2004-2007 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + Implementation of the weak / associative references for non-GC mode. +*/ + + +#include "objc-private.h" +#include + + +// wrap all the murky C++ details in a namespace to get them out of the way. + +namespace objc_references_support { + struct ObjcPointerEqual { + bool operator()(void *p1, void *p2) const { + return p1 == p2; + } + }; + + struct ObjcPointerClear { + void operator() (void *pointer) { + void **location = (void **)pointer; + *location = NULL; + } + }; + + struct ObjcPointerHash { + uintptr_t operator()(void *p) const { + uintptr_t k = (uintptr_t)p; + + // borrowed from CFSet.c + #if __LP64__ + uintptr_t a = 0x4368726973746F70ULL; + uintptr_t b = 0x686572204B616E65ULL; + #else + uintptr_t a = 0x4B616E65UL; + uintptr_t b = 0x4B616E65UL; + #endif + uintptr_t c = 1; + a += k; + #if __LP64__ + a -= b; a -= c; a ^= (c >> 43); + b -= c; b -= a; b ^= (a << 9); + c -= a; c -= b; c ^= (b >> 8); + a -= b; a -= c; a ^= (c >> 38); + b -= c; b -= a; b ^= (a << 23); + c -= a; c -= b; c ^= (b >> 5); + a -= b; a -= c; a ^= (c >> 35); + b -= c; b -= a; b ^= (a << 49); + c -= a; c -= b; c ^= (b >> 11); + a -= b; a -= c; a ^= (c >> 12); + b -= c; b -= a; b ^= (a << 18); + c -= a; c -= b; c ^= (b >> 22); + #else + a -= b; a -= c; a ^= (c >> 13); + b -= c; b -= a; b ^= (a << 8); + c -= a; c -= b; c ^= (b >> 13); + a -= b; a -= c; a ^= (c >> 12); + b -= c; b -= a; b ^= (a << 16); + c -= a; c -= b; c ^= (b >> 5); + a -= b; a -= c; a ^= (c >> 3); + b -= c; b -= a; b ^= (a << 10); + c -= a; c -= b; c ^= (b >> 15); + #endif + return c; + } + }; + + // STL allocator that uses the runtime's internal allocator. + + template struct ObjcAllocator { + typedef T value_type; + typedef value_type* pointer; + typedef const value_type *const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template struct rebind { typedef ObjcAllocator other; }; + + template ObjcAllocator(const ObjcAllocator&) {} + ObjcAllocator() {} + ObjcAllocator(const ObjcAllocator&) {} + ~ObjcAllocator() {} + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { + return x; + } + + pointer allocate(size_type n, const_pointer = 0) { + return static_cast(::_malloc_internal(n * sizeof(T))); + } + + void deallocate(pointer p, size_type) { ::_free_internal(p); } + + size_type max_size() const { + return static_cast(-1) / sizeof(T); + } + + void construct(pointer p, const value_type& x) { + new(p) value_type(x); + } + + void destroy(pointer p) { p->~value_type(); } + + void operator=(const ObjcAllocator&); + + }; + + template<> struct ObjcAllocator { + typedef void value_type; + typedef void* pointer; + typedef const void *const_pointer; + template struct rebind { typedef ObjcAllocator other; }; + }; + + struct ObjcAssociation { + uintptr_t policy; + id value; + ObjcAssociation(uintptr_t newPolicy, id newValue) : policy(newPolicy), value(newValue) { } + ObjcAssociation() : policy(0), value(0) { } + }; + + // typedef vector > PtrVector; + // typedef hash_set > PtrHashSet; + // typedef hash_map > PtrPtrHashMap; +#if TARGET_OS_WIN32 + typedef hash_map ObjectAssocationHashMap; + typedef hash_map AssocationsHashMap; +#else + typedef hash_map > ObjectAssocationHashMap; + typedef hash_map > AssocationsHashMap; +#endif +} + +using namespace objc_references_support; + +// class AssociationsManager manages a lock / hash table singleton pair. +// Allocating an instance acquires the lock, and calling its assocations() method +// lazily allocates it. + +class AssociationsManager { + static OSSpinLock _lock; + static AssocationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap. +public: + AssociationsManager() { OSSpinLockLock(&_lock); } + ~AssociationsManager() { OSSpinLockUnlock(&_lock); } + + AssocationsHashMap &associations() { + if (_map == NULL) + _map = new(::_malloc_internal(sizeof(AssocationsHashMap))) AssocationsHashMap(); + return *_map; + } +}; + +OSSpinLock AssociationsManager::_lock = OS_SPINLOCK_INIT; +AssocationsHashMap *AssociationsManager::_map = NULL; + +// expanded policy bits. + +enum { + OBJC_ASSOCIATION_SETTER_ASSIGN = 0, + OBJC_ASSOCIATION_SETTER_RETAIN = 1, + OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. + OBJC_ASSOCIATION_GETTER_READ = (0 << 8), + OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), + OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) +}; + +__private_extern__ id _object_get_associative_reference(id object, void *key) { + id value = nil; + uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; + { + AssociationsManager manager; + AssocationsHashMap &associations(manager.associations()); + AssocationsHashMap::iterator i = associations.find(object); + if (i != associations.end()) { + ObjectAssocationHashMap &refs = i->second; + ObjectAssocationHashMap::iterator j = refs.find(key); + if (j != refs.end()) { + ObjcAssociation &entry = j->second; + value = (id)entry.value; + policy = entry.policy; + if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) objc_msgSend(value, SEL_retain); + } + } + } + if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { + objc_msgSend(value, SEL_autorelease); + } + return value; +} + +static id acquireValue(id value, uintptr_t policy) { + switch (policy & 0xFF) { + case OBJC_ASSOCIATION_SETTER_RETAIN: + return objc_msgSend(value, SEL_retain); + case OBJC_ASSOCIATION_SETTER_COPY: + return objc_msgSend(value, SEL_copy); + } + return value; +} + +static void releaseValue(id value, uintptr_t policy) { + if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { + objc_msgSend(value, SEL_release); + } +} + +struct ReleaseValue { + void operator() (ObjcAssociation &association) { + releaseValue(association.value, association.policy); + } +}; + +__private_extern__ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { + // retain the new value (if any) outside the lock. + uintptr_t old_policy = 0; // NOTE: old_policy is always assigned to when old_value is non-nil. + id new_value = value ? acquireValue(value, policy) : nil, old_value = nil; + { + AssociationsManager manager; + AssocationsHashMap &associations(manager.associations()); + if (new_value) { + // break any existing association. + AssocationsHashMap::iterator i = associations.find(object); + if (i != associations.end()) { + // secondary table exists + ObjectAssocationHashMap &refs = i->second; + ObjectAssocationHashMap::iterator j = refs.find(key); + if (j != refs.end()) { + ObjcAssociation &old_entry = j->second; + old_policy = old_entry.policy; + old_value = old_entry.value; + old_entry.policy = policy; + old_entry.value = new_value; + } else { + refs[key] = ObjcAssociation(policy, new_value); + } + } else { + // create the new association (first time). + associations[object][key] = ObjcAssociation(policy, new_value); + _class_assertInstancesHaveAssociatedObjects(object->isa); + } + } else { + // setting the association to nil breaks the association. + AssocationsHashMap::iterator i = associations.find(object); + if (i != associations.end()) { + ObjectAssocationHashMap &refs = i->second; + ObjectAssocationHashMap::iterator j = refs.find(key); + if (j != refs.end()) { + ObjcAssociation &old_entry = j->second; + old_policy = old_entry.policy; + old_value = (id) old_entry.value; + refs.erase(j); + } + } + } + } + // release the old value (outside of the lock). + if (old_value) releaseValue(old_value, old_policy); +} + +__private_extern__ void _object_remove_assocations(id object) { + vector elements; + { + AssociationsManager manager; + AssocationsHashMap &associations(manager.associations()); + if (associations.size() == 0) return; + AssocationsHashMap::iterator i = associations.find(object); + if (i != associations.end()) { + // copy all of the associations that need to be removed. + ObjectAssocationHashMap &refs = i->second; + for (ObjectAssocationHashMap::iterator j = refs.begin(); j != refs.end(); ++j) { + elements.push_back(j->second); + } + // remove the secondary table. + associations.erase(i); + } + } + // the calls to releaseValue() happen outside of the lock. + for_each(elements.begin(), elements.end(), ReleaseValue()); +}