#ifndef _OBJC_OBJCOBJECT_H_
#define _OBJC_OBJCOBJECT_H_
#include "objc-private.h"
static ALWAYS_INLINE bool fastAutoreleaseForReturn(id obj);
static ALWAYS_INLINE bool fastRetainFromReturn(id obj);
#if SUPPORT_TAGGED_POINTERS
#define TAG_COUNT 8
#define TAG_SLOT_MASK 0xf
#if SUPPORT_MSB_TAGGED_POINTERS
# define TAG_MASK (1ULL<<63)
# define TAG_SLOT_SHIFT 60
# define TAG_PAYLOAD_LSHIFT 4
# define TAG_PAYLOAD_RSHIFT 4
#else
# define TAG_MASK 1
# define TAG_SLOT_SHIFT 0
# define TAG_PAYLOAD_LSHIFT 0
# define TAG_PAYLOAD_RSHIFT 4
#endif
extern "C" { extern Class objc_debug_taggedpointer_classes[TAG_COUNT*2]; }
#define objc_tag_classes objc_debug_taggedpointer_classes
#endif
inline bool
objc_object::isClass()
{
if (isTaggedPointer()) return false;
return ISA()->isMetaClass();
}
#if SUPPORT_NONPOINTER_ISA
# if !SUPPORT_TAGGED_POINTERS
# error sorry
# endif
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
inline bool
objc_object::hasIndexedIsa()
{
return isa.indexed;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initClassIsa(Class cls)
{
if (DisableIndexedIsa) {
initIsa(cls, false, false);
} else {
initIsa(cls, true, false);
}
}
inline void
objc_object::initProtocolIsa(Class cls)
{
return initClassIsa(cls);
}
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!UseGC);
assert(!cls->requiresRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!indexed) {
isa.cls = cls;
} else {
assert(!DisableIndexedIsa);
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
inline Class
objc_object::changeIsa(Class newCls)
{
assert(!isTaggedPointer());
isa_t oldisa;
isa_t newisa;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
if ((oldisa.bits == 0 || oldisa.indexed) &&
newCls->canAllocIndexed())
{
if (oldisa.bits == 0) newisa.bits = ISA_MAGIC_VALUE;
else newisa = oldisa;
newisa.has_cxx_dtor = newCls->hasCxxDtor();
newisa.shiftcls = (uintptr_t)newCls >> 3;
}
else if (oldisa.indexed) {
if (!sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.cls = newCls;
}
else {
newisa.cls = newCls;
}
} while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));
if (transcribeToSideTable) {
sidetable_moveExtraRC_nolock(oldisa.extra_rc,
oldisa.deallocating,
oldisa.weakly_referenced);
}
if (sideTableLocked) sidetable_unlock();
Class oldCls;
if (oldisa.indexed) oldCls = (Class)((uintptr_t)oldisa.shiftcls << 3);
else oldCls = oldisa.cls;
return oldCls;
}
inline bool
objc_object::isTaggedPointer()
{
return ((uintptr_t)this & TAG_MASK);
}
inline bool
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa.indexed) return isa.has_assoc;
return true;
}
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.indexed) return;
if (newisa.has_assoc) return;
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
inline bool
objc_object::isWeaklyReferenced()
{
assert(!isTaggedPointer());
if (isa.indexed) return isa.weakly_referenced;
else return sidetable_isWeaklyReferenced();
}
inline void
objc_object::setWeaklyReferenced_nolock()
{
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.indexed) return sidetable_setWeaklyReferenced_nolock();
if (newisa.weakly_referenced) return;
newisa.weakly_referenced = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
inline bool
objc_object::hasCxxDtor()
{
assert(!isTaggedPointer());
if (isa.indexed) return isa.has_cxx_dtor;
else return isa.cls->hasCxxDtor();
}
inline bool
objc_object::rootIsDeallocating()
{
assert(!UseGC);
if (isTaggedPointer()) return false;
if (isa.indexed) return isa.deallocating;
return sidetable_isDeallocating();
}
inline void
objc_object::clearDeallocating()
{
if (!isa.indexed) {
sidetable_clearDeallocating();
}
else if (isa.weakly_referenced) {
clearDeallocating_weak();
}
assert(!sidetable_present());
}
inline void
objc_object::rootDealloc()
{
assert(!UseGC);
if (isTaggedPointer()) return;
if (isa.indexed &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor)
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
inline id
objc_object::retain()
{
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{
return rootRetain(true, false) ? true : false;
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (!newisa.indexed) goto unindexed;
if (tryRetain && newisa.deallocating) goto tryfail;
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);
if (carry) {
if (!handleOverflow) return rootRetain_overflow(tryRetain);
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));
if (transcribeToSideTable) {
sidetable_addExtraRC_nolock(RC_HALF);
}
if (!tryRetain && sideTableLocked) sidetable_unlock();
return (id)this;
tryfail:
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
unindexed:
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
inline void
objc_object::release()
{
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootReleaseShouldDealloc()
{
return rootRelease(false, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
assert(!UseGC);
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (!newisa.indexed) goto unindexed;
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); if (carry) goto underflow;
} while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits));
if (sideTableLocked) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
if (newisa.has_sidetable_rc) {
if (!handleUnderflow) {
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) sidetable_lock();
sideTableLocked = true;
newisa.extra_rc = RC_HALF - 1; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
bool zeroed = sidetable_subExtraRC_nolock(RC_HALF);
if (zeroed) {
do {
oldisa = LoadExclusive(&isa.bits);
newisa.has_sidetable_rc = false;
} while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));
}
sidetable_unlock();
return false;
}
if (sideTableLocked) sidetable_unlock();
if (newisa.deallocating) {
return overrelease_error();
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
unindexed:
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
inline id
objc_object::autorelease()
{
assert(!UseGC || ISA()->hasCustomRR());
if (isTaggedPointer()) return (id)this;
if (! ISA()->hasCustomRR()) return rootAutorelease();
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
inline id
objc_object::rootAutorelease()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
if (fastAutoreleaseForReturn((id)this)) return (id)this;
return rootAutorelease2();
}
inline uintptr_t
objc_object::rootRetainCount()
{
assert(!UseGC);
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
if (bits.indexed) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
#else
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return isa.cls;
}
inline bool
objc_object::hasIndexedIsa()
{
return false;
}
inline Class
objc_object::getIsa()
{
#if SUPPORT_TAGGED_POINTERS
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
#endif
return ISA();
}
inline void
objc_object::initIsa(Class cls)
{
assert(!isTaggedPointer());
isa = (uintptr_t)cls;
}
inline void
objc_object::initClassIsa(Class cls)
{
initIsa(cls);
}
inline void
objc_object::initProtocolIsa(Class cls)
{
initIsa(cls);
}
inline void
objc_object::initInstanceIsa(Class cls, bool)
{
initIsa(cls);
}
inline void
objc_object::initIsa(Class cls, bool, bool)
{
initIsa(cls);
}
inline Class
objc_object::changeIsa(Class cls)
{
assert(!isTaggedPointer());
isa_t oldisa, newisa;
newisa.cls = cls;
do {
oldisa = LoadExclusive(&isa.bits);
} while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits));
if (oldisa.cls && oldisa.cls->instancesHaveAssociatedObjects()) {
cls->setInstancesHaveAssociatedObjects();
}
return oldisa.cls;
}
inline bool
objc_object::isTaggedPointer()
{
#if SUPPORT_TAGGED_POINTERS
return ((uintptr_t)this & TAG_MASK);
#else
return false;
#endif
}
inline bool
objc_object::hasAssociatedObjects()
{
assert(!UseGC);
return getIsa()->instancesHaveAssociatedObjects();
}
inline void
objc_object::setHasAssociatedObjects()
{
assert(!UseGC);
getIsa()->setInstancesHaveAssociatedObjects();
}
inline bool
objc_object::isWeaklyReferenced()
{
assert(!isTaggedPointer());
assert(!UseGC);
return sidetable_isWeaklyReferenced();
}
inline void
objc_object::setWeaklyReferenced_nolock()
{
assert(!isTaggedPointer());
assert(!UseGC);
sidetable_setWeaklyReferenced_nolock();
}
inline bool
objc_object::hasCxxDtor()
{
assert(!isTaggedPointer());
return isa.cls->hasCxxDtor();
}
inline bool
objc_object::rootIsDeallocating()
{
assert(!UseGC);
if (isTaggedPointer()) return false;
return sidetable_isDeallocating();
}
inline void
objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
object_dispose((id)this);
}
inline id
objc_object::retain()
{
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
return sidetable_retain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
inline id
objc_object::rootRetain()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
inline void
objc_object::release()
{
assert(!UseGC || ISA()->hasCustomRR());
assert(!isTaggedPointer());
if (! ISA()->hasCustomRR()) {
sidetable_release();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
inline bool
objc_object::rootRelease()
{
assert(!UseGC);
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
inline bool
objc_object::rootReleaseShouldDealloc()
{
if (isTaggedPointer()) return false;
return sidetable_release(false);
}
inline id
objc_object::autorelease()
{
assert(!UseGC || ISA()->hasCustomRR());
if (isTaggedPointer()) return (id)this;
if (! ISA()->hasCustomRR()) return rootAutorelease();
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
inline id
objc_object::rootAutorelease()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
if (fastAutoreleaseForReturn((id)this)) return (id)this;
return rootAutorelease2();
}
inline bool
objc_object::rootTryRetain()
{
assert(!UseGC);
if (isTaggedPointer()) return true;
return sidetable_tryRetain();
}
inline uintptr_t
objc_object::rootRetainCount()
{
assert(!UseGC);
if (isTaggedPointer()) return (uintptr_t)this;
return sidetable_retainCount();
}
#endif
#if SUPPORT_RETURN_AUTORELEASE
# if __x86_64__
static ALWAYS_INLINE bool
callerAcceptsFastAutorelease(const void * const ra0)
{
const uint8_t *ra1 = (const uint8_t *)ra0;
const uint16_t *ra2;
const uint32_t *ra4 = (const uint32_t *)ra1;
const void **sym;
#define PREFER_GOTPCREL 0
#if PREFER_GOTPCREL
if (*ra4 != 0xffc78948) {
return false;
}
if (ra1[4] != 0x15) {
return false;
}
ra1 += 3;
#else
if (*ra4 != 0xe8c78948) {
return false;
}
ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l;
ra2 = (const uint16_t *)ra1;
if (*ra2 != 0x25ff) {
return false;
}
#endif
ra1 += 6l + (long)*(const int32_t *)(ra1 + 2);
sym = (const void **)ra1;
if (*sym != objc_retainAutoreleasedReturnValue)
{
return false;
}
return true;
}
# elif __arm__
static ALWAYS_INLINE bool
callerAcceptsFastAutorelease(const void *ra)
{
if ((uintptr_t)ra & 1) {
if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
return true;
}
} else {
if (*(uint32_t *)ra == 0xe1a07007) {
return true;
}
}
return false;
}
# elif __arm64__
static ALWAYS_INLINE bool
callerAcceptsFastAutorelease(const void *ra)
{
if (*(uint32_t *)ra == 0xaa1d03fd) {
return true;
}
return false;
}
# elif __i386__ && TARGET_IPHONE_SIMULATOR
static inline bool
callerAcceptsFastAutorelease(const void *ra)
{
return false;
}
# else
#warning unknown architecture
static ALWAYS_INLINE bool
callerAcceptsFastAutorelease(const void *ra)
{
return false;
}
# endif
static ALWAYS_INLINE
bool fastAutoreleaseForReturn(id obj)
{
assert(tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY) == nil);
if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
return true;
}
return false;
}
static ALWAYS_INLINE
bool fastRetainFromReturn(id obj)
{
if (obj == tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY)) {
tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, 0);
return true;
}
return false;
}
#else
static ALWAYS_INLINE
bool fastAutoreleaseForReturn(id obj)
{
return false;
}
static ALWAYS_INLINE
bool fastRetainFromReturn(id obj)
{
return false;
}
#endif
#endif