]> git.saurik.com Git - apple/objc4.git/blobdiff - runtime/objc-arr.mm
objc4-493.9.tar.gz
[apple/objc4.git] / runtime / objc-arr.mm
diff --git a/runtime/objc-arr.mm b/runtime/objc-arr.mm
new file mode 100644 (file)
index 0000000..0bcd995
--- /dev/null
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (c) 2010-2011 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@
+ */
+
+#include "llvm-DenseMap.h"
+
+#import "objc-weak.h"
+#import "objc-private.h"
+#import "objc-internal.h"
+#import "objc-os.h"
+#import "runtime.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+//#include <fcntl.h>
+#include <mach/mach.h>
+#include <mach-o/dyld.h>
+#include <mach-o/nlist.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <libkern/OSAtomic.h>
+#include <Block.h>
+#include <map>
+#include <execinfo.h>
+
+#if SUPPORT_RETURN_AUTORELEASE
+// We cannot peek at where we are returning to unless we always inline this:
+__attribute__((always_inline))
+static bool callerAcceptsFastAutorelease(const void * const ra0);
+#endif
+
+
+/***********************************************************************
+* Weak ivar support
+**********************************************************************/
+
+static bool seen_weak_refs;
+
+@protocol ReferenceCounted
++ (id)alloc;
++ (id)allocWithZone:(malloc_zone_t *)zone;
+- (oneway void)dealloc;
+- (id)retain;
+- (oneway void)release;
+- (id)autorelease;
+- (uintptr_t)retainCount;
+@end
+
+#define ARR_LOGGING 0
+
+#if ARR_LOGGING
+struct {
+    int retains;
+    int releases;
+    int autoreleases;
+    int blockCopies;
+} CompilerGenerated, ExplicitlyCoded;
+
+PRIVATE_EXTERN void (^objc_arr_log)(const char *, id param) = 
+    ^(const char *str, id param) { printf("%s %p\n", str, param); };
+#endif
+
+
+namespace {
+
+#if TARGET_OS_EMBEDDED
+#   define SIDE_TABLE_STRIPE 1
+#else
+#   define SIDE_TABLE_STRIPE 8
+#endif
+
+// should be a multiple of cache line size (64)
+#define SIDE_TABLE_SIZE 64
+
+typedef objc::DenseMap<id,size_t,true> RefcountMap;
+
+class SideTable {
+private:
+    static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
+
+public:
+    OSSpinLock slock;
+    RefcountMap refcnts;
+    weak_table_t weak_table;
+
+    SideTable() : slock(OS_SPINLOCK_INIT)
+    {
+        memset(&weak_table, 0, sizeof(weak_table));
+    }
+    
+    ~SideTable() 
+    {
+        // never delete side_table in case other threads retain during exit
+        assert(0);
+    }
+
+    static SideTable *tableForPointer(const void *p) 
+    {
+#     if SIDE_TABLE_STRIPE == 1
+        return (SideTable *)table_buf;
+#     else
+        uintptr_t a = (uintptr_t)p;
+        int index = ((a >> 4) ^ (a >> 9)) & (SIDE_TABLE_STRIPE - 1);
+        return (SideTable *)&table_buf[index * SIDE_TABLE_SIZE];
+#     endif
+    }
+
+    static void init() {
+        // use placement new instead of static ctor to avoid dtor at exit
+        for (int i = 0; i < SIDE_TABLE_STRIPE; i++) {
+            new (&table_buf[i * SIDE_TABLE_SIZE]) SideTable;
+        }
+    }
+};
+
+STATIC_ASSERT(sizeof(SideTable) <= SIDE_TABLE_SIZE);
+__attribute__((aligned(SIDE_TABLE_SIZE))) uint8_t 
+SideTable::table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
+
+// Avoid false-negative reports from tools like "leaks"
+#define DISGUISE(x) ((id)~(uintptr_t)(x))
+
+// anonymous namespace
+};
+
+
+//
+// The -fobjc-arr flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
+//
+
+id objc_retainBlock(id x) {
+#if ARR_LOGGING
+    objc_arr_log("objc_retain_block", x);
+    ++CompilerGenerated.blockCopies;
+#endif
+    return (id)_Block_copy(x);
+}
+
+//
+// The following SHOULD be called by the compiler directly, but the request hasn't been made yet :-)
+//
+
+BOOL objc_should_deallocate(id object) {
+    return YES;
+}
+
+// WORKAROUND:
+// <rdar://problem/9038601> clang remembers variadic bit across function cast
+// <rdar://problem/9048030> Clang thinks that all ObjC vtable dispatches are variadic
+// <rdar://problem/8873428> vararg function defeats tail-call optimization
+id objc_msgSend_hack(id, SEL) asm("_objc_msgSend");
+
+// public API entry points that might be optimized later
+
+__attribute__((aligned(16)))
+id
+objc_retain(id obj)
+{
+       return objc_msgSend_hack(obj, @selector(retain));
+}
+
+__attribute__((aligned(16)))
+void
+objc_release(id obj)
+{
+       objc_msgSend_hack(obj, @selector(release));
+}
+
+__attribute__((aligned(16)))
+id
+objc_autorelease(id obj)
+{
+       return objc_msgSend_hack(obj, @selector(autorelease));
+}
+
+id
+objc_retain_autorelease(id obj)
+{
+       return objc_autorelease(objc_retain(obj));
+}
+
+id
+objc_storeWeak(id *location, id newObj)
+{
+    id oldObj;
+    SideTable *oldTable;
+    SideTable *newTable;
+    OSSpinLock *lock1;
+#if SIDE_TABLE_STRIPE > 1
+    OSSpinLock *lock2;
+#endif
+
+    if (!seen_weak_refs) {
+        seen_weak_refs = true;
+    }
+
+    // Acquire locks for old and new values.
+    // Order by lock address to prevent lock ordering problems. 
+    // Retry if the old value changes underneath us.
+ retry:
+    oldObj = *location;
+    
+    oldTable = SideTable::tableForPointer(oldObj);
+    newTable = SideTable::tableForPointer(newObj);
+    
+    lock1 = &newTable->slock;
+#if SIDE_TABLE_STRIPE > 1
+    lock2 = &oldTable->slock;
+    if (lock1 > lock2) {
+        OSSpinLock *temp = lock1;
+        lock1 = lock2;
+        lock2 = temp;
+    }
+    if (lock1 != lock2) OSSpinLockLock(lock2);
+#endif
+    OSSpinLockLock(lock1);
+
+    if (*location != oldObj) {
+        OSSpinLockUnlock(lock1);
+#if SIDE_TABLE_STRIPE > 1
+        if (lock1 != lock2) OSSpinLockUnlock(lock2);
+#endif
+        goto retry;
+    }
+
+    if (oldObj) {
+        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
+    }
+    if (newObj) {
+        newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
+        // weak_register_no_lock returns NULL if weak store should be rejected
+    }
+    // Do not set *location anywhere else. That would introduce a race.
+    *location = newObj;
+    
+    OSSpinLockUnlock(lock1);
+#if SIDE_TABLE_STRIPE > 1
+    if (lock1 != lock2) OSSpinLockUnlock(lock2);
+#endif
+
+    return newObj;
+}
+
+id
+objc_loadWeakRetained(id *location)
+{
+    id result;
+
+    SideTable *table;
+    OSSpinLock *lock;
+    
+ retry:
+    result = *location;
+    if (!result) return NULL;
+    
+    table = SideTable::tableForPointer(result);
+    lock = &table->slock;
+    
+    OSSpinLockLock(lock);
+    if (*location != result) {
+        OSSpinLockUnlock(lock);
+        goto retry;
+    }
+
+    result = arr_read_weak_reference(&table->weak_table, location);
+
+    OSSpinLockUnlock(lock);
+    return result;
+}
+
+id
+objc_loadWeak(id *location)
+{
+    return objc_autorelease(objc_loadWeakRetained(location));
+}
+
+id
+objc_initWeak(id *addr, id val)
+{
+       *addr = 0;
+       return objc_storeWeak(addr, val);
+}
+
+void
+objc_destroyWeak(id *addr)
+{
+       objc_storeWeak(addr, 0);
+}
+
+void
+objc_copyWeak(id *to, id *from)
+{
+       id val = objc_loadWeakRetained(from);
+       objc_initWeak(to, val);
+       objc_release(val);
+}
+
+void
+objc_moveWeak(id *to, id *from)
+{
+       objc_copyWeak(to, from);
+       objc_destroyWeak(from);
+}
+
+
+/* Autorelease pool implementation
+   A thread's autorelease pool is a stack of pointers. 
+   Each pointer is either an object to release, or POOL_SENTINEL which is 
+     an autorelease pool boundary.
+   A pool token is a pointer to the POOL_SENTINEL for that pool. When 
+     the pool is popped, every object hotter than the sentinel is released.
+   The stack is divided into a doubly-linked list of pages. Pages are added 
+     and deleted as necessary. 
+   Thread-local storage points to the hot page, where newly autoreleased 
+     objects are stored. 
+ */
+
+extern "C" BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
+
+namespace {
+
+struct magic_t {
+    static const uint32_t M0 = 0xA1A1A1A1;
+#   define M1 "AUTORELEASE!"
+    static const size_t M1_len = 12;
+    uint32_t m[4];
+    
+    magic_t() {
+        assert(M1_len == strlen(M1));
+        assert(M1_len == 3 * sizeof(m[1]));
+
+        m[0] = M0;
+        strncpy((char *)&m[1], M1, M1_len);
+    }
+
+    ~magic_t() {
+        m[0] = m[1] = m[2] = m[3] = 0;
+    }
+
+    bool check() const {
+        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
+    }
+
+    bool fastcheck() const {
+#ifdef NDEBUG
+        return (m[0] == M0);
+#else
+        return check();
+#endif
+    }
+
+#   undef M1
+};
+    
+
+// Set this to 1 to mprotect() autorelease pool contents
+#define PROTECT_AUTORELEASEPOOL 0
+
+class AutoreleasePoolPage 
+{
+
+#define POOL_SENTINEL 0
+    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
+    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
+    static size_t const SIZE = 
+#if PROTECT_AUTORELEASEPOOL
+        4096;  // must be multiple of vm page size
+#else
+        4096;  // size and alignment, power of 2
+#endif
+    static size_t const COUNT = SIZE / sizeof(id);
+
+    magic_t const magic;
+    id *next;
+    pthread_t const thread;
+    AutoreleasePoolPage * const parent;
+    AutoreleasePoolPage *child;
+    uint32_t const depth;
+    uint32_t hiwat;
+
+    // SIZE-sizeof(*this) bytes of contents follow
+
+    static void * operator new(size_t size) {
+        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
+    }
+    static void operator delete(void * p) {
+        return free(p);
+    }
+
+    inline void protect() {
+#if PROTECT_AUTORELEASEPOOL
+        mprotect(this, SIZE, PROT_READ);
+        check();
+#endif
+    }
+
+    inline void unprotect() {
+#if PROTECT_AUTORELEASEPOOL
+        check();
+        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
+#endif
+    }
+
+    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
+        : magic(), next(begin()), thread(pthread_self()),
+          parent(newParent), child(NULL), 
+          depth(parent ? 1+parent->depth : 0), 
+          hiwat(parent ? parent->hiwat : 0)
+    { 
+        if (parent) {
+            parent->check();
+            assert(!parent->child);
+            parent->unprotect();
+            parent->child = this;
+            parent->protect();
+        }
+        protect();
+    }
+
+    ~AutoreleasePoolPage() 
+    {
+        check();
+        unprotect();
+        assert(empty());
+
+        // Not recursive: we don't want to blow out the stack 
+        // if a thread accumulates a stupendous amount of garbage
+        assert(!child);
+    }
+
+
+    void busted(bool die = true) 
+    {
+        (die ? _objc_fatal : _objc_inform)
+            ("autorelease pool page %p corrupted\n"
+             "  magic %x %x %x %x\n  pthread %p\n", 
+             this, magic.m[0], magic.m[1], magic.m[2], magic.m[3], 
+             this->thread);
+    }
+
+    void check(bool die = true) 
+    {
+        if (!magic.check() || !pthread_equal(thread, pthread_self())) {
+            busted(die);
+        }
+    }
+
+    void fastcheck(bool die = true) 
+    {
+        if (! magic.fastcheck()) {
+            busted(die);
+        }
+    }
+
+
+    id * begin() {
+        return (id *) ((uint8_t *)this+sizeof(*this));
+    }
+
+    id * end() {
+        return (id *) ((uint8_t *)this+SIZE);
+    }
+
+    bool empty() {
+        return next == begin();
+    }
+
+    bool full() { 
+        return next == end();
+    }
+
+    bool lessThanHalfFull() {
+        return (next - begin() < (end() - begin()) / 2);
+    }
+
+    id *add(id obj)
+    {
+        assert(!full());
+        unprotect();
+        *next++ = obj;
+        protect();
+        return next-1;
+    }
+
+    void releaseAll() 
+    {
+        releaseUntil(begin());
+    }
+
+    void releaseUntil(id *stop) 
+    {
+        // Not recursive: we don't want to blow out the stack 
+        // if a thread accumulates a stupendous amount of garbage
+        
+        while (this->next != stop) {
+            // Restart from hotPage() every time, in case -release 
+            // autoreleased more objects
+            AutoreleasePoolPage *page = hotPage();
+
+            // fixme I think this `while` can be `if`, but I can't prove it
+            while (page->empty()) {
+                page = page->parent;
+                setHotPage(page);
+            }
+
+            page->unprotect();
+            id obj = *--page->next;
+            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
+            page->protect();
+
+            if (obj != POOL_SENTINEL) {
+                objc_release(obj);
+            }
+        }
+
+        setHotPage(this);
+
+#ifndef NDEBUG
+        // we expect any children to be completely empty
+        for (AutoreleasePoolPage *page = child; page; page = page->child) {
+            assert(page->empty());
+        }
+#endif
+    }
+
+    void kill() 
+    {
+        // Not recursive: we don't want to blow out the stack 
+        // if a thread accumulates a stupendous amount of garbage
+        AutoreleasePoolPage *page = this;
+        while (page->child) page = page->child;
+
+        AutoreleasePoolPage *deathptr;
+        do {
+            deathptr = page;
+            page = page->parent;
+            if (page) {
+                page->unprotect();
+                page->child = NULL;
+                page->protect();
+            }
+            delete deathptr;
+        } while (deathptr != this);
+    }
+
+    static void tls_dealloc(void *p) 
+    {
+        // reinstate TLS value while we work
+        setHotPage((AutoreleasePoolPage *)p);
+        pop(0);
+        setHotPage(NULL);
+    }
+
+    static AutoreleasePoolPage *pageForPointer(const void *p) 
+    {
+        return pageForPointer((uintptr_t)p);
+    }
+
+    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
+    {
+        AutoreleasePoolPage *result;
+        uintptr_t offset = p % SIZE;
+
+        assert(offset >= sizeof(AutoreleasePoolPage));
+
+        result = (AutoreleasePoolPage *)(p - offset);
+        result->fastcheck();
+
+        return result;
+    }
+
+
+    static inline AutoreleasePoolPage *hotPage() 
+    {
+        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
+            _pthread_getspecific_direct(key);
+        if (result) result->fastcheck();
+        return result;
+    }
+
+    static inline void setHotPage(AutoreleasePoolPage *page) 
+    {
+        if (page) page->fastcheck();
+        _pthread_setspecific_direct(key, (void *)page);
+    }
+
+    static inline AutoreleasePoolPage *coldPage() 
+    {
+        AutoreleasePoolPage *result = hotPage();
+        if (result) {
+            while (result->parent) {
+                result = result->parent;
+                result->fastcheck();
+            }
+        }
+        return result;
+    }
+
+
+    static inline id *autoreleaseFast(id obj)
+    {
+        AutoreleasePoolPage *page = hotPage();
+        if (page && !page->full()) {
+            return page->add(obj);
+        } else {
+            return autoreleaseSlow(obj);
+        }
+    }
+
+    static __attribute__((noinline))
+    id *autoreleaseSlow(id obj)
+    {
+        AutoreleasePoolPage *page;
+        page = hotPage();
+
+        // The code below assumes some cases are handled by autoreleaseFast()
+        assert(!page || page->full());
+
+        if (!page) {
+            assert(obj != POOL_SENTINEL);
+            _objc_inform("Object %p of class %s autoreleased "
+                         "with no pool in place - just leaking - "
+                         "break on objc_autoreleaseNoPool() to debug", 
+                         obj, object_getClassName(obj));
+            objc_autoreleaseNoPool(obj);
+            return NULL;
+        }
+
+        do {
+            if (page->child) page = page->child;
+            else page = new AutoreleasePoolPage(page);
+        } while (page->full());
+
+        setHotPage(page);
+        return page->add(obj);
+    }
+
+public:
+    static inline id autorelease(id obj)
+    {
+        assert(obj);
+        assert(!OBJC_IS_TAGGED_PTR(obj));
+        id *dest __unused = autoreleaseFast(obj);
+        assert(!dest  ||  *dest == obj);
+        return obj;
+    }
+
+
+    static inline void *push() 
+    {
+        if (!hotPage()) {
+            setHotPage(new AutoreleasePoolPage(NULL));
+        } 
+        id *dest = autoreleaseFast(POOL_SENTINEL);
+        assert(*dest == POOL_SENTINEL);
+        return dest;
+    }
+
+    static inline void pop(void *token) 
+    {
+        AutoreleasePoolPage *page;
+        id *stop;
+
+        if (token) {
+            page = pageForPointer(token);
+            stop = (id *)token;
+            assert(*stop == POOL_SENTINEL);
+        } else {
+            // Token 0 is top-level pool
+            page = coldPage();
+            assert(page);
+            stop = page->begin();
+        }
+
+        if (PrintPoolHiwat) printHiwat();
+
+        page->releaseUntil(stop);
+
+        // memory: delete empty children
+        // hysteresis: keep one empty child if this page is more than half full
+        // special case: delete everything for pop(0)
+        if (!token) {
+            page->kill();
+            setHotPage(NULL);
+        } else if (page->child) {
+            if (page->lessThanHalfFull()) {
+                page->child->kill();
+            }
+            else if (page->child->child) {
+                page->child->child->kill();
+            }
+        }
+    }
+
+    static void init()
+    {
+        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
+                                             AutoreleasePoolPage::tls_dealloc);
+        assert(r == 0);
+    }
+
+    void print() 
+    {
+        _objc_inform("[%p]  ................  PAGE %s %s %s", this, 
+                     full() ? "(full)" : "", 
+                     this == hotPage() ? "(hot)" : "", 
+                     this == coldPage() ? "(cold)" : "");
+        check(false);
+        for (id *p = begin(); p < next; p++) {
+            if (*p == POOL_SENTINEL) {
+                _objc_inform("[%p]  ################  POOL %p", p, p);
+            } else {
+                _objc_inform("[%p]  %#16lx  %s", 
+                             p, (unsigned long)*p, object_getClassName(*p));
+            }
+        }
+    }
+
+    static void printAll()
+    {        
+        _objc_inform("##############");
+        _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
+
+        AutoreleasePoolPage *page;
+        ptrdiff_t objects = 0;
+        for (page = coldPage(); page; page = page->child) {
+            objects += page->next - page->begin();
+        }
+        _objc_inform("%llu releases pending.", (unsigned long long)objects);
+
+        for (page = coldPage(); page; page = page->child) {
+            page->print();
+        }
+
+        _objc_inform("##############");
+    }
+
+    static void printHiwat()
+    {
+        // Check and propagate high water mark
+        // Ignore high water marks under 256 to suppress noise.
+        AutoreleasePoolPage *p = hotPage();
+        uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
+        if (mark > p->hiwat  &&  mark > 256) {
+            for( ; p; p = p->parent) {
+                p->unprotect();
+                p->hiwat = mark;
+                p->protect();
+            }
+            
+            _objc_inform("POOL HIGHWATER: new high water mark of %u "
+                         "pending autoreleases for thread %p:", 
+                         mark, pthread_self());
+            
+            void *stack[128];
+            int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
+            char **sym = backtrace_symbols(stack, count);
+            for (int i = 0; i < count; i++) {
+                _objc_inform("POOL HIGHWATER:     %s", sym[i]);
+            }
+                free(sym);
+        }
+    }
+
+#undef POOL_SENTINEL
+};
+
+// anonymous namespace
+};
+
+// API to only be called by root classes like NSObject or NSProxy
+
+extern "C" {
+__attribute__((used,noinline,nothrow))
+static id _objc_rootRetain_slow(id obj);
+__attribute__((used,noinline,nothrow))
+static bool _objc_rootReleaseWasZero_slow(id obj);
+};
+
+id
+_objc_rootRetain_slow(id obj)
+{
+    SideTable *table = SideTable::tableForPointer(obj);
+    OSSpinLockLock(&table->slock);
+    table->refcnts[DISGUISE(obj)] += 2;
+    OSSpinLockUnlock(&table->slock);
+
+    return obj;
+}
+
+id
+_objc_rootRetain(id obj)
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return obj;
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    if (OSSpinLockTry(&table->slock)) {
+        table->refcnts[DISGUISE(obj)] += 2;
+        OSSpinLockUnlock(&table->slock);
+        return obj;
+    }
+    return _objc_rootRetain_slow(obj);
+}
+
+bool
+_objc_rootTryRetain(id obj) 
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return true;
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    // NO SPINLOCK HERE
+    // _objc_rootTryRetain() is called exclusively by _objc_loadWeak(), 
+    // which already acquired the lock on our behalf.
+    if (table->slock == 0) {
+        _objc_fatal("Do not call -_tryRetain.");
+    }
+
+    bool result = true;
+    RefcountMap::iterator it = table->refcnts.find(DISGUISE(obj));
+    if (it == table->refcnts.end()) {
+        table->refcnts[DISGUISE(obj)] = 2;
+    } else if (it->second & 1) {
+        result = false;
+    } else {
+        it->second += 2;
+    }
+    
+    return result;
+}
+
+bool
+_objc_rootIsDeallocating(id obj) 
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return false;
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    // NO SPINLOCK HERE
+    // _objc_rootIsDeallocating() is called exclusively by _objc_storeWeak(), 
+    // which already acquired the lock on our behalf.
+    if (table->slock == 0) {
+        _objc_fatal("Do not call -_isDeallocating.");
+    }
+
+    RefcountMap::iterator it = table->refcnts.find(DISGUISE(obj));
+    return (it != table->refcnts.end()) && ((it->second & 1) == 1);
+}
+
+
+void 
+objc_clear_deallocating(id obj) 
+{
+    assert(obj);
+    assert(!UseGC);
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    // clear any weak table items
+    // clear extra retain count and deallocating bit
+    // (fixme warn or abort if extra retain count == 0 ?)
+    OSSpinLockLock(&table->slock);
+    if (seen_weak_refs) {
+        arr_clear_deallocating(&table->weak_table, obj);
+    }
+    table->refcnts.erase(DISGUISE(obj));
+    OSSpinLockUnlock(&table->slock);
+}
+
+
+bool
+_objc_rootReleaseWasZero_slow(id obj)
+{
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    bool do_dealloc = false;
+
+    OSSpinLockLock(&table->slock);
+    RefcountMap::iterator it = table->refcnts.find(DISGUISE(obj));
+    if (it == table->refcnts.end()) {
+        do_dealloc = true;
+        table->refcnts[DISGUISE(obj)] = 1;
+    } else if (it->second == 0) {
+        do_dealloc = true;
+        it->second = 1;
+    } else {
+        it->second -= 2;
+    }
+    OSSpinLockUnlock(&table->slock);
+    return do_dealloc;
+}
+
+bool
+_objc_rootReleaseWasZero(id obj)
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return false;
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    bool do_dealloc = false;
+
+    if (OSSpinLockTry(&table->slock)) {
+        RefcountMap::iterator it = table->refcnts.find(DISGUISE(obj));
+        if (it == table->refcnts.end()) {
+            do_dealloc = true;
+            table->refcnts[DISGUISE(obj)] = 1;
+        } else if (it->second == 0) {
+            do_dealloc = true;
+            it->second = 1;
+        } else {
+            it->second -= 2;
+        }
+        OSSpinLockUnlock(&table->slock);
+        return do_dealloc;
+    }
+    return _objc_rootReleaseWasZero_slow(obj);
+}
+
+void
+_objc_rootRelease(id obj)
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (_objc_rootReleaseWasZero(obj) == false) {
+        return;
+    }
+    objc_msgSend_hack(obj, @selector(dealloc));
+}
+
+__attribute__((noinline,used))
+static id _objc_rootAutorelease2(id obj)
+{
+    if (OBJC_IS_TAGGED_PTR(obj)) return obj;
+    return AutoreleasePoolPage::autorelease(obj);
+}
+
+__attribute__((aligned(16)))
+id
+_objc_rootAutorelease(id obj)
+{
+    assert(obj); // root classes shouldn't get here, since objc_msgSend ignores nil
+    assert(!UseGC);
+
+    if (UseGC) {
+        return obj;
+    }
+
+    // no tag check here: tagged pointers DO use fast autoreleasing
+
+#if SUPPORT_RETURN_AUTORELEASE
+    assert(_pthread_getspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY) == NULL);
+
+    if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
+        _pthread_setspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
+        return obj;
+    }
+#endif
+    return _objc_rootAutorelease2(obj);
+}
+
+uintptr_t
+_objc_rootRetainCount(id obj)
+{
+    assert(obj);
+    assert(!UseGC);
+
+    // XXX -- There is no way that anybody can use this API race free in a
+    // threaded environment because the result is immediately stale by the
+    // time the caller receives it.
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return (uintptr_t)obj;    
+
+    SideTable *table = SideTable::tableForPointer(obj);
+
+    size_t refcnt_result = 1;
+    
+    OSSpinLockLock(&table->slock);
+    RefcountMap::iterator it = table->refcnts.find(DISGUISE(obj));
+    if (it != table->refcnts.end()) {
+        refcnt_result = (it->second >> 1) + 1;
+    }
+    OSSpinLockUnlock(&table->slock);
+    return refcnt_result;
+}
+
+id
+_objc_rootInit(id obj)
+{
+       // In practice, it will be hard to rely on this function.
+       // Many classes do not properly chain -init calls.
+       return obj;
+}
+
+id
+_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
+{
+#if __OBJC2__
+       // allocWithZone under __OBJC2__ ignores the zone parameter
+       (void)zone;
+       return class_createInstance(cls, 0);
+#else
+       if (!zone || UseGC) {
+               return class_createInstance(cls, 0);
+       }
+       return class_createInstanceFromZone(cls, 0, zone);
+#endif
+}
+
+id
+_objc_rootAlloc(Class cls)
+{
+#if 0
+       // once we get a bit in the class, data structure, we can call this directly
+       // because allocWithZone under __OBJC2__ ignores the zone parameter
+       return class_createInstance(cls, 0);
+#else
+       return [cls allocWithZone: nil];
+#endif
+}
+
+void
+_objc_rootDealloc(id obj)
+{
+    assert(obj);
+    assert(!UseGC);
+
+    if (OBJC_IS_TAGGED_PTR(obj)) return;
+
+    object_dispose(obj);
+}
+
+void
+_objc_rootFinalize(id obj __unused)
+{
+    assert(obj);
+    assert(UseGC);
+
+    if (UseGC) {
+        return;
+    }
+    _objc_fatal("_objc_rootFinalize called with garbage collection off");
+}
+
+malloc_zone_t *
+_objc_rootZone(id obj)
+{
+       (void)obj;
+       if (gc_zone) {
+               return gc_zone;
+       }
+#if __OBJC2__
+       // allocWithZone under __OBJC2__ ignores the zone parameter
+       return malloc_default_zone();
+#else
+       malloc_zone_t *rval = malloc_zone_from_ptr(obj);
+       return rval ? rval : malloc_default_zone();
+#endif
+}
+
+uintptr_t
+_objc_rootHash(id obj)
+{
+       if (UseGC) {
+               return _object_getExternalHash(obj);
+       }
+       return (uintptr_t)obj;
+}
+
+// make CF link for now
+void *_objc_autoreleasePoolPush(void) { return objc_autoreleasePoolPush(); }
+void _objc_autoreleasePoolPop(void *ctxt) { objc_autoreleasePoolPop(ctxt); }
+
+void *
+objc_autoreleasePoolPush(void)
+{
+    if (UseGC) return NULL;
+    return AutoreleasePoolPage::push();
+}
+
+void
+objc_autoreleasePoolPop(void *ctxt)
+{
+    if (UseGC) return;
+
+    // fixme rdar://9167170
+    if (!ctxt) return;
+
+    AutoreleasePoolPage::pop(ctxt);
+}
+
+void 
+_objc_autoreleasePoolPrint(void)
+{
+    if (UseGC) return;
+    AutoreleasePoolPage::printAll();
+}
+
+#if SUPPORT_RETURN_AUTORELEASE
+
+/*
+  Fast handling of returned autoreleased values.
+  The caller and callee cooperate to keep the returned object 
+  out of the autorelease pool.
+
+  Caller:
+    ret = callee();
+    objc_retainAutoreleasedReturnValue(ret);
+    // use ret here
+
+  Callee:
+    // compute ret
+    [ret retain];
+    return objc_autoreleaseReturnValue(ret);
+
+  objc_autoreleaseReturnValue() examines the caller's instructions following
+  the return. If the caller's instructions immediately call
+  objc_autoreleaseReturnValue, then the callee omits the -autorelease and saves
+  the result in thread-local storage. If the caller does not look like it
+  cooperates, then the callee calls -autorelease as usual.
+
+  objc_autoreleaseReturnValue checks if the returned value is the same as the
+  one in thread-local storage. If it is, the value is used directly. If not,
+  the value is assumed to be truly autoreleased and is retained again.  In
+  either case, the caller now has a retained reference to the value.
+
+  Tagged pointer objects do participate in the fast autorelease scheme, 
+  because it saves message sends. They are not entered in the autorelease 
+  pool in the slow case.
+*/
+
+# if __x86_64__
+
+static 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
+    // 48 89 c7    movq  %rax,%rdi
+    // ff 15       callq *symbol@GOTPCREL(%rip)
+    if (*ra4 != 0xffc78948) {
+        return false;
+    }
+    if (ra1[4] != 0x15) {
+        return false;
+    }
+    ra1 += 3;
+#else
+    // 48 89 c7    movq  %rax,%rdi
+    // e8          callq symbol
+    if (*ra4 != 0xe8c78948) {
+        return false;
+    }
+    ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l;
+    ra2 = (const uint16_t *)ra1;
+    // ff 25       jmpq *symbol@DYLDMAGIC(%rip)
+    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;
+}
+
+// __x86_64__
+# else
+
+#warning unknown architecture
+
+static bool callerAcceptsFastAutorelease(const void *ra)
+{
+    return false;
+}
+
+# endif
+
+// SUPPORT_RETURN_AUTORELEASE
+#endif
+
+
+id 
+objc_autoreleaseReturnValue(id obj)
+{
+#if SUPPORT_RETURN_AUTORELEASE
+    assert(_pthread_getspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY) == NULL);
+
+    if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
+        _pthread_setspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
+        return obj;
+    }
+#endif
+
+    return objc_autorelease(obj);
+}
+
+id 
+objc_retainAutoreleaseReturnValue(id obj)
+{
+    return objc_autoreleaseReturnValue(objc_retain(obj));
+}
+
+id
+objc_retainAutoreleasedReturnValue(id obj)
+{
+#if SUPPORT_RETURN_AUTORELEASE
+    if (obj == _pthread_getspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY)) {
+        _pthread_setspecific_direct(AUTORELEASE_POOL_RECLAIM_KEY, 0);
+        return obj;
+    }
+#endif
+    return objc_retain(obj);
+}
+
+void
+objc_storeStrong(id *location, id obj)
+{
+       // XXX FIXME -- GC support?
+       id prev = *location;
+       if (obj == prev) {
+               return;
+       }
+       objc_retain(obj);
+       *location = obj;
+       objc_release(prev);
+}
+
+id
+objc_retainAutorelease(id obj)
+{
+       return objc_autorelease(objc_retain(obj));
+}
+
+void
+_objc_deallocOnMainThreadHelper(void *context)
+{
+       id obj = (id)context;
+       objc_msgSend_hack(obj, @selector(dealloc));
+}
+
+// convert objc_objectptr_t to id, callee must take ownership.
+NS_RETURNS_RETAINED id objc_retainedObject(objc_objectptr_t CF_CONSUMED pointer) { return (id)pointer; }
+
+// convert objc_objectptr_t to id, without ownership transfer.
+NS_RETURNS_NOT_RETAINED id objc_unretainedObject(objc_objectptr_t pointer) { return (id)pointer; }
+
+// convert id to objc_objectptr_t, no ownership transfer.
+CF_RETURNS_NOT_RETAINED objc_objectptr_t objc_unretainedPointer(id object) { return object; }
+
+
+PRIVATE_EXTERN void arr_init(void) 
+{
+    AutoreleasePoolPage::init();
+    SideTable::init();
+}