Objective-C autoreleasepool深入理解
Objective-C autorelease
// main.m int main(int argc, char * argv[]) { @autoreleasepool { } }
clang -rewrite-objc main.m
__AtAutoreleasePool __autoreleasepool;
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; };
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 0x%08x 0x%08x 0x%08x 0x%08x\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 *) tls_get_direct(key); if (result) result->fastcheck(); return result; } static inline void setHotPage(AutoreleasePoolPage *page) { if (page) page->fastcheck(); tls_set_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 };
https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html
@autoreleasepool
双向链表