Thursday, January 22, 2009

Foundation on Darwin (attempt #1)

The PureDarwin project seems to be coming along in leaps and bounds, recently releasing the PureDarwin Xmas VMWare image. Since this gives me a working Darwin install to play with — and since I've had some spare time this week — I thought I'd take a look at what it would take to get a binary compatible Foundation working on Darwin (a project which I've tentatively entitled "PureFoundation"). By 'binary compatible' I mean compile on OS X and run on Darwin. Since Apple gives us the Objective-C 2.0 runtime, the AutoZone Garbage Collector, and CFLite, how hard could it be?

As I'm sure you're aware, many CoreFoundation (henceforth CF) objects are toll-free bridged to their Foundation counterparts. To most intents and purposes they are identical structures. Since CFLite implements reference counting (and the documentation and source code even hints that it plays nicely with the Garbage Collector) I hit upon what I thought would be a simple approach: rather than having a mix of pure objective-C and bridged classes, I'd create a CF class which I could bridge to NSObject, meaning that all the "PureFoundation" objects inherited CF's memory management.

Long(-ish) story short, I got this working. More or less. I could compile a simple Foundation.framework in XCode and copy it across to the Darwin VM; compile a simple Cocoa command line app, also in XCode, which created an NSObject, sent it -retain and -release, and reported it's retainCount. The same binary ran in an identical fashion under both OS X and Darwin. At this point I was about ready to call up the Nobel people and suggest they started a computing prize just for me. But...

CF objects are almost identical to objective-C structures. They're 8 (or 12 under 64-bit) byte structures, the first 4 (or 8) bytes of which are the isa pointer used to identify whether the object is an objective-C class and if so which one. The remaining 4 bytes holds other info, such as their CFTypeID and the lower 16 bits of the retain count. Since NSObject only allocates a single 4 (or 8) byte isa pointer, I had to pad the NSObject in my "PureFoundation" framework with another 4 dummy bytes, meaning that while the proper NSObject on OS X took up 4 bytes, my NSObject on (32-bit) Darwin took 8.

Now, I had (rather over optimistically) hoped that the dynamism of the objective-C runtime stretched to defining Classes and assigning ivars. So that if you defined a class which extended NSObject by adding extra variables, the runtime — rather than the compiler — would allocated them after whichever already existed. The runtime certainly provides functions for doing so. Unfortunately, this isn't the case. Testing an NSObject subclass with one extra NSUInteger ivar, compiled in XCode, showed it took up 8 bytes on both OS X (correctly) and Darwin (aargh!). Under "PureFoundation", retain and release worked fine immediately after the CF object was allocated, but once the -init ran the reference counting area of memory got stomped on.

So it's back to the drawing board. Next stop is the GNUStep source code, to see how they implement release/retain. The GNUStep source was always going to come into play at some point, so I guess this isn't too big a problem. It also looks like there's no getting away from patching CFLite to reinstate the bridging functions which were turned into no-ops by Apple before they released it. Which means setting up a darwinbuild environment and coding in Nano. Oh, well.

No comments: