On Oct 21, 2011, at 19:37, Cole wrote:
> i understand that this is a open source project, and there is no
> guarantee or timelines or anything.
> and i understand that if i wanted it bad enough, i could try to do it.
> but i was wondering if there was anyone already working on it, or if
> it was in anyone's "plan" to do.
In case anyone’s interested in pursuing this, I’d like to point out that the semantics of the ARC runtime functions are quite trivial, if you exclude weak pointer support. Without weak pointers, you get the same level of ARC support as when targeting OS X 10.6 using the 10.7 tool set.
I've posted such a trivial implementation of the runtime here: https://gist.github.com/1385556. This compiles but hasn't been tested, since i don't currently have a VM set up, or a Cocotron-friendly Clang.
Outline of weak pointer support
(Clean-room concerns: the following is based both on documented semantics and the behaviour of the Apple runtime. It refers to specifics of Apple runtime behaviour, but the algorithm outlines are written without direct reference to the Apple code.)
There are seven weak reference runtime functions, but five are defined in terms of others leaving two primitives, objc_storeWeak() and objc_loadWeakRetained(). -release/-dealloc make up third primitive.
Locking issues are largely ignored in this discussion. Using a single lock shared between the three primitives is semantically sufficient, although the Apple runtime uses a more complex scheme for efficiency.
In order to be able to clear weak references when deallocating, the runtime must maintain a list of weak reference sites for each object which has at least one. This can be done out-of-band in the same way as extra refcounts and associated objects. These lists are managed completely by the objc_storeWeak primitive, except in the case of deallocating an object which has outstanding weak references.
Ignoring locking, objc_storeWeak(id *destination, id newValue) must do the following:
If newValue != nil:
If it responds to -allowsWeakReference, assert [newValue allowsWeakReference].
Otherwise, set newValue to nil.
Load *destination into oldValue.
If oldValue != newValue:
If oldValue != nil, remove destination from oldValue’s weak reference list.
If newValue != nil, add destination to newValue’s weak reference list.
-[NSObject allowsWeakReference], is undocumented, listed in NSObject.h, but marked NS_UNAVAILABLE. Calling it under normal circumstances causes an assertion failure in the runtime. I believe it makes the following checks:
That retain is not overridden.
That release is not overridden.
That the object is not being deallocated, as reported by the private method _isDeallocating. (This is defined in the runtime, and causes the assertion failure. The assertion is there because the caller is responsible for managing the runtime-internal locking.)
_isDeallocating returns a per-object out-of-band flag which is set immediately before calling -dealloc. (In the Apple runtime, this is stored in the low bit of the extra refcount store. It looks as though extra refcounts are not removed when they reach one.)
The second primitive, objc_loadWeakRetained(id *source), is conceptually simple: take the necessary lock, then load *source, and retain and return it if it is not nil. As with the store, it also needs to ensure that the object is not currently being deallocated.
Locking aside, we have:
Load *source into result.
If result is not nil:
Assert ![result _isDeallocating].
Retain result.
Return result.
In Apple’s runtime the deallocating check and retain are done by sending retainWeakReference, which in turn calls _tryRetain (it doesn’t seem to need to do anything else, so it’s not clear why there are two methods; _tryRetain is part of the runtime source, while retainWeakReference is not).
Like _isDeallocating, _tryRetain crashes if it isn’t called by the runtime, so it’s not necessary to reproduce the exact details. As far as I can see, asserting _isDeallocating would be semantically equivalent, and the separate _tryRetain is an optimization.
The _isDeallocating flag is set by -release before calling -dealloc. Root -dealloc itself (or rather, object_dispose()) simply needs to iterate over the list of weak references and write nil to them, then delete the list and remove it from the hash.
Lifetime management and autorelease pool optimization
A general theme of Apple’s ARC support is that object lifetime management has been moved into the runtime. The runtime is now responsible for object allocation and destruction, reference counting and autorelease pools.
The ARC entry points are designed to support a couple of significant optimizations: NSAutoreleasePool-free autorelease pools and eliminating unnecessary autoreleases of return values at runtime.
The first of these is relatively simple, and is actually independent of ARC: first, the autorelease pool store is flattened (per thread), and second, the now-trivial NSAutoreleasePool objects are eliminated. Flattening means that instead of each autorelease pool having its own list of autoreleased objects – which in Cocotron use 1024-entry pages – there is a single list per thread.
Each autorelease pool stores a cursor into this per-thread list. Draining an autorelease pool means releasing all objects beyond its cursor in the list and then resetting the end of the list to the pool’s cursor. (This automatically drains any child pools, so explicit parent-child relationships are not needed.) Autoreleasing an object simply appends it to the thread list, with no need to look up the current autorelease pool.
Since NSAutoreleasePools are now just wrappers around pool cursors, they can simply be eliminated when using @autoreleasepool syntax: instead of objc_autoreleasePoolPush() creating an NSAutoreleasePool, it can simply return the current pool cursor, and objc_autoreleasePoolPop() can perform the draining behaviour outlined above. NSAutoreleasePools for non-ARC code then become wrappers around these functions, although a stack of NSAutoreleasePools is still needed for proper cleanup of the NSAutoreleasePool objects themselves.
In cases where custom autorelease pools are used, especially recursively, this should be a significant speed improvement due to cache locality and reduced allocation overhead.
(I’ve implemented autorelease pools along these lines, but my code could be considered “contaminated” by APSL since I was quite familiar with Apple’s implementation. It’s pretty simple, and I’ll probably write a blog post about it when I’ve cleaned it up a bit.)
The second optimization, eliminating autoreleases has been described by Mike Ash (see http://mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-co... search for objc_retainAutoreleaseReturnValue). It’s pretty nifty, but it relies on recognizing specific machine code patterns that depend on both the target architecture and the compiler. I think the maintenance cost is unreasonable for the Cocotron runtime.
--
Jens Ayton