Nat! bio photo

Nat!

Senior Mull.

Twitter RSS

Github

"hello world" with the MulleFoundation

The mulle_objc Foundation was able to compile it's first "Hello World!" program. A milestone :) It is is actually more involved than it might seem.

Here is the code:

#import <Foundation/Foundation.h>

#ifndef MULLE_OBJC_POSIX_FOUNDATION_VERSION
# error "compiling with the wrong Foundation"
#endif


// if this compiles, we know that the headers are properly setup

int main(int argc, const char * argv[])
{
   NSLog( @"%@", @"Hello, World!");
   return 0;
}

Where is the autorelease pool ? Every thread gets one NSAutoreleasePool for free in this runtime.

Importing the proper Foundation on OS X

This is easy, since the compiler allows us to specify framework paths to be searched ahead of the system search paths, so the Xcode default -F$(BUILD_PRODUCTS_DIR) is good enough, when the Foundation is in the same project.

Linking the proper Foundation on OS X

Is actually harder (on OS X). One has to reset the linker search path with -Z and then add the search paths manually into OTHER_LFDLAGS

OTHER_LDFLAGS = $(inherited) -t -Z -L/usr/lib -F$(BUILT_PRODUCTS_DIR)

Running the program (on OS X)

So lets run the HelloWorld example. Since the Foundation framework is in the current working directory besides the HelloWorld program, OSX has no problem finding it (otherwise it would expect to find it in /Library/Frameworks (should be @rpath, actually)) as otool shows us:

./HelloWorld:
    /Library/Frameworks/Foundation.framework/Versions/A/Foundation (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
    /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 913.0.0)

The Foundation can be made to detect leaks and incorrect frees. Every allocation in the Foundation is funneled through an allocator structure that looks like this:

struct mulle_allocator
{
   void   *(*calloc)( size_t n, size_t size);
   void   *(*realloc)( void *block, size_t size);
   void   (*free)( void *block);
   int    (*abafree)( void *aba, void (*free)( void *), void *block);
   void   *aba;
};

a test allocator records each malloc operation and checks if it is correct or not. The option to use it can be turned on with an environment variable, so the actual invocation is:

MULLE_OBJC_TEST_ALLOCATOR=1 MallocStackLogging=1 \
./HelloWorld

And this is the output:

Foundation uses "mulle_test_allocator_objc" to detect leaks.
mulle_objc_runtime warning: different typed selectors "v16@0:8" and "i16@0:8" for selector "load"
Hello, World!
leak 0x100405670
sudo malloc_history 17710 0x100405670
malloc_history Report Version:  2.0
...
ALLOC 0x100405670-0x10040567f [size=16]: thread_7fff760a3300 |start | _start | main | NSLog | NSLogv | mulle_objc_object_call | _mulle_objc_call_class_needs_cache | mulle_objc_object_call_class | +[NSString(Sprintf) stringWithFormat:va_list:] | mulle_objc_object_call | mulle_objc_object_call_class | +[MulleObjCClassCluster alloc] | MulleObjCNewClassClusterPlaceholder | NSAllocateObject | _MulleObjCAllocateObject | MulleObjCAllocateMemory | _mulle_allocator_calloc | test_calloc_or_raise | _mulle_allocator_calloc | test_calloc | calloc | malloc_zone_calloc

It's nice to have this allocation checker, since It detects leaks pretty much immediately.

Scrutinizing the output

The runtime warning

The warning mulle_objc_runtime warning: different typed selectors "v16@0:8" and "i16@0:8" for selector "load" appears, because there is + (void) load on all classes and - (BOOL) load on NSBundle.

The "Hello World" output

It is actually fairly involved to produce the required output for

   NSLog( @"%@", @"Hello, World!");

NSLog and NSLogv are straightforward:

void   NSLog( NSString *format, ...)
{
   va_list   args;

   va_start( args, format );
   NSLogv( format, args);
   va_end( args);
}


void   NSLogv( NSString *format, va_list args)
{
   NSString  *s;
   char      *cString;

   s = [NSString stringWithFormat:format
                          va_list:args];
   cString = [s cString];
   syslog( __NSLogPriority, cString, NULL, NULL, NULL);
   fprintf( stderr, "%s\n", cString);
}

As NSLog is a C function, its parameters were not subject to the MetaABI. They are passed in with the default C ABI, therefore NSLogv has to deal with va_list instead of mulle_vararg_list as an ObjC method would have to.

NSConstantString

The static strings "%@" and @"Hello World" will have been produced by the compiler. Though the compiler could know, that the class of the static strings is "NSConstantString", it's up to the foundation to implement it. The isa pointers of these constant strings are nil during compilation and linking.

When NSConstantString executes it's +load method it calls _mulle_objc_runtime_set_staticstringclass to install itself into the runtime as the static string class. At this time all known static strings are patched with the proper isa:

@implementation NSConstantString

+ (void) load
{
   struct _mulle_objc_runtime   *runtime;
   struct _mulle_objc_class     *cls;

   runtime = __get_or_create_objc_runtime();
   cls     = _mulle_objc_class_get_infraclass( (void *) self);
   assert( cls);
   _mulle_objc_runtime_set_staticstringclass( runtime, cls);
}

NSString + Unicode

NSString now needs to create an instance from the arbitrary format and the parameters contained in the va_list.

What is a NSString in the MulleFoundation anyway ? It is a Unicode string like in the Apple Foundation, except that unichar is defined to be int32_t instead of unsigned short.

That sounds like a good idea at the moment, though I am not that deeply into unicode.

To make NSString efficient for -characterAtIndex: the Foundation uses the following reasoning:

  1. technical strings are likely to be 7bit ASCII
  2. user strings are likely to be one of the european languages or japanese or chinese, these fit (AFAIK) into UTF16 (w/o surrogate pairs, the UTF16 extension mechanism)
  3. all other strings are rare and will use full UTF32
  4. BOM characters are never stored

So input characters are analyzed and then the NSString class cluster decides, if it creates an instance with internal (zero terminated) ASCII, UTF16 or UTF32 representation.

NSString + sprintf

NSString needs a way to sprintf objects using %@. The C function sprintf(3) itself can not do this, so there exists mulle-sprintf which has a plugin model so that additional conversion characters can be installed.

mulle-sprintf is a C library, which doesn't operate on NSString per se. The format has to be converted into a UTF8String first. The UTF8String method of the various NSString subclasses do just that, and to keep this efficient for strings which are often queried for UTF8String, the non-ASCII NSStrings maintain a UTF8 shadow of themselves.

The actual conversion then is simple, all the difficult stuff is handled by mulle_sprintf.

int  mulle_string_sprintf_object_conversion( struct mulle_buffer *buffer,
                                             struct mulle_sprintf_formatconversioninfo *info,
                                             struct mulle_sprintf_argumentarray *arguments,
                                             int argc)
{
   extern int   mulle_sprintf_cstring_conversion( struct mulle_buffer *buffer,
                                                  struct mulle_sprintf_formatconversioninfo *info,
                                                  char *s);
   union mulle_sprintf_argumentvalue  v;
   char                               *s;

   v = arguments->values[ argc];
   s = v.obj ? (char *) [[(id) v.obj description] UTF8String] : "(nil)";

   return( mulle_sprintf_cstring_conversion( buffer, info, s));
}

struct mulle_buffer is a byte stream, a growing buffer that can be sequentially written.

syslog needs which encoding ?

Finally we would like to output the result using syslog. C functions generally expect the string to be encoded in the characterset of the running OS in the user locale.

The solution to this problem is cString. cString is defined on each platform to produce the appropriate encoded char sequence. On OSX it's just the same as UTF8String.

A leak!

leak 0x100405670
sudo malloc_history 17710 0x100405670
ALLOC 0x100405670-0x10040567f [size=16]: thread_7fff760a3300 |start |
_start | main |
NSLog | NSLogv | mulle_objc_object_call | _mulle_objc_call_class_needs_cache | mulle_objc_object_call_class |
+[NSString(Sprintf) stringWithFormat:va_list:] | mulle_objc_object_call |mulle_objc_object_call_class |
+[MulleObjCClassCluster alloc] | MulleObjCNewClassClusterPlaceholder |
NSAllocateObject | _MulleObjCAllocateObject | MulleObjCAllocateMemory |
_mulle_allocator_calloc | test_calloc_or_raise | _mulle_allocator_calloc |
test_calloc | calloc | malloc_zone_calloc

The Foundation has detected that something hasn't been freed properly. With malloc_history and MallocStackLogging (OS X) only, it's easy to locate, where the allocation was made, that leaked.

The stacktrace indicates, that I likely forgot to autorelease the placeholder object in my -init method. This is of course a bug (that I will fix).

If you look at the stacktrace, you will see +[MulleObjCClassCluster alloc] being called from [NSString(Sprintf) stringWithFormat:va_list:]. Does that mean, that NSString subclasses MulleObjCClassCluster ?

No NSString conforms to the the MulleObjCClassCluster protocol, and inherits it's methods. All it has to do is this:

@interface NSString( ClassCluster)  < MulleObjCClassCluster>
@end

Final Words

So that was a quick tour of Hello World in mulle_objc. You got a glimpse of what it does and how it does it. You can be sure that there are still a lot of other neat things to cover.


Post a comment

All comments are held for moderation; basic HTML formatting accepted.

Name:
E-mail: (not published)
Website: