Objective-C for the AVR, part 1



This is part one in an occasional series about my AVR Objective-C runtime. See also my subsequent entries on the topic (2, 3, 4).

I have an affinity for tech that peaked in the early 2000s—and a lot of time on my hands. So I decided to try running some Objective-C on my AVR microcontrollers.

A search for prior art turned up a trove of amazing comments. Among my favorites:

"No, you cannot program Arduino using Objective-C." (source)

No, objective-c never worked on avr architecture. There are some chinese forums where you can get more info, but basicly that's it. (source)

"It seems like you are misunderstanding the Arduino, and the microcontroller <-> PC relationship." (source)

"It can be done, however, it is inadvisable. In order to implement this, one must implement the Objective-C runtime, which, though not insurmountable, is very near insurmountable as far as difficulty is concerned. [...] Perhaps someday some brave soul will see fit to grace the world with their custom Arduino Objective-C runtime, but until that day, the rest of us mere mortals have naught to do but wait for the coming of the Objective-C prophet. [...] If there are any brave souls in the audience who wish to take on this ultimate challenge, then let them. But otherwise, keep the code in C and hold your peace." (source)

Were these people intentionally trying to bait me into this project? Who knows, but either way, it worked.


GCC is the standard compiler used for AVR development these days, and it's had support for Objective-C ever since it was first implemented at NeXT over 30 years ago. So, with the cross/avr-gcc port installed, I first tried the naïve approach of just hoping it already worked:

$ echo '@interface Foo @end' | avr-gcc -xobjective-c -
avr-gcc: error: language objective-c not recognized

My subsequent attempt to get past this error message led me down a fun rabbit hole that I'm aiming to document here. The main code is available on GitHub, with a few other bits of code linked along the way.

Building GCC

Unfortunately, as far as I could tell, all the standard avr-gcc distributions have the Objective-C frontend disabled. So the first thing I did was to build my own GCC to enable it.

I started with the GCC 10.2.0 source, for no reason except that it was the version I already had downloaded. Which frontends are enabled is controlled by the --enable-languages parameter to the configure script, for example:[1]

$ ../src/configure --enable-languages=c,objc --with-avrlibc --prefix /opt/local --build x86_64-apple-darwin19.6.0 --host x86_64-apple-darwin19.6.0 --target avr

The remaining parameters are the standard set-up for building a cross compiler.


Trying to build with the above configure command will technically work, but the subsequent build step takes approximately forever.

Building GCC also builds its target libraries—compiler-provided runtime libraries used by programs built by the compiler. The compiler and target libraries are version-locked; there's no expectation that compiler A will work with target libraries B.

In many cases, building the target libraries is a small portion of the total build time. But because GCC builds its target libraries according to a system called multilib, their size and build time can vary significantly based on the target architecture. I discussed multilib variants in a previous blog entry involving the GNU toolchain for embedded ARM.

As it happens, the multilib situation is really complicated for the AVR target. The various AVR microcontrollers are categorized into sixteen different families, based on various shared characteristics, like support for particular instructions or program memory size. (The details aren't important, but they can be extracted from avr-devices.c and avr-mcus.def in the GCC source.)

Furthermore, each family itself generates multiple floating point multilib configurations. The C standard defines floating-point types float, double, and long double; double must be a superset of float and long double a superset of double.

Since the AVRs have no floating-point hardware, all floating-point expressions are evaluated in software, by calls into the compiler runtime library. For example, addition of two doubles is compiled into a subroutine call to __adddf3. The various software float routines are somewhat documented here. In any case, it's clear that the exact implementations of those routines will depend on the binary format of the floating-point types.

For AVRs, GCC supports 32 bits for float and 32 or 64 bits for both double and long double. That means there are three valid combinations: float=32, double=32, long double=32; float=32, double=32, long double=64; and float=32, double=64, long double=64.

Additionally, the avr2 and avr25 families support a variant called tiny-stack, and the avrxmega3 family supports a variant called short-calls.

All told, therefore, GCC wants to build its target libraries a total of 57 ways. That basically means that full builds of GCC take a really, really long time, especially on the low-ish-end machine I'm working on.

Luckily, GCC provides a --with-multilib-list argument to configure that controls which multilibs are build. Unluckily, it wasn't implemented for the AVR target! So I implemented it and submitted a patch. The core of the change is to the config/avr/genmultilib.awk script; that script's output is used as arguments to genmultilib, which produces C source called multilib_raw, which is built into GCC. multilib_raw is a string that describes the set of multilib configurations supported by the GCC binary; that, in turn, informs the build system how many times (and which ways) to build the target libs.

Simple, right? In these moments, what helps me is to break out pen and paper and write out a dependency graph, so that's what I did:

Hand-written dependency graph for GCC AVR multilib configurations

Yeah, GCC's build system is arcane and, to be frank, more than a little overengineered, and figuring out the right places to change was admittedly quite a challenge.[2] But the change ended up being pretty simple in terms of lines of diff, and it's saved me many hours of time.

With the --with-multilib-list functionality working, I was able to use a more targeted configure invocation:

$ ../src/configure --enable-languages=c,objc --with-avrlibc --prefix /opt/local --build x86_64-apple-darwin19.6.0 --host x86_64-apple-darwin19.6.0 --target avr --with-multilib-list=avr5 --with-double=64 --with-long-double=64

(I'm specifying avr5 here since that covers the ATmega644p I'm working with.)

This tells GCC to build a much smaller set of multilibs. (For weird reasons, somewhat described in this bug report, "avr2" still needs to be built, even though it's unused, since it's the default value for -mmcu.) And only one configuration of floating-point types is needed.

Runtime

The core of Objective-C is its dynamic message dispatch, which is implemented by a runtime library. The runtime also usually provides/supports other language features, like RTTI, reference counting, non-fragile ivars, memory management, and exceptions.

So once I had the compiler built, I needed to provide a runtime. There are a handful of options here, none of which I ended up using, but awareness of which is useful as background.

Apple's objc4 (modern)

This is the runtime used on all of Apple's major platforms (macOS and derivatives). It's the de facto reference implementation, and it supports all the features a modern-day Objective-C programmer is familiar with.

This runtime is only used by Apple, and as such the code contains lots of dependencies on lower-level bits of Darwin. Some are "SPIs" (APIs not exposed in the public SDK).[3] Others are publicly available—but not in my bare-bones environment, like libpthread (my own simple AVR threading library isn't a sufficient replacement).

Its message dispatch routines are hand-coded assembly, meaning that I'd need to rewrite them for the AVR anyway.

Apple/NeXT's objc4 (legacy)

This is an earlier version of the Apple runtime, with no support for certain newer features, most notably non-fragile ivars.[4] Apple calls this the legacy runtime and used it for the i386 and PowerPC slices on Mac OS X; before that, it was the runtime on NeXTSTEP.

One interesting aspect of the legacy runtime is how it handles class methods. Invocation of a class method, like this:

@interface Foo
+ (void)doSomething;
@end

int main(void) {
    [Foo doSomething];
}

is compiled into this (shown as i386 assembly):

L_OBJC_CLASS_REFERENCES_:
    .long   L_OBJC_CLASS_NAME_
L_OBJC_CLASS_NAME_:
    .asciz  "Foo"
L_OBJC_SELECTOR_REFERENCES_:
    .long   L_OBJC_METH_VAR_NAME_
L_OBJC_METH_VAR_NAME_:
    .asciz  "doSomething"

main:
    movl    L_OBJC_CLASS_REFERENCES_-L1$pb(%eax), %ecx
    movl    L_OBJC_SELECTOR_REFERENCES_-L1$pb(%eax), %eax
    movl    %ecx, (%esp)
    movl    %eax, 4(%esp)
    calll   _objc_msgSend

For a call to objc_msgSend, the zeroth argument ((%esp) in the stack-based calling convention in use here) is normally filled in with a pointer to the receiver object—in this case, a class object.[5] But here, the value passed is the long loaded from L_OBJC_CLASS_REFERENCES_, which is a pointer to a C string. C strings are not Objective-C objects! How does that work?

The answer, as so often, is runtime magic. When Objective-C code is loaded into a process's address space, dyld (the dynamic linker on macOS) invokes the runtime, which scans L_OBJC_CLASS_REFERENCES_, replacing each pointer-to-C-string with the corresponding pointer-to-class-object. This work is done by the _objc_map_class_refs_for_image function, source of which is here.

I'm honestly not entirely sure why it was originally designed this way. One guess is that the Objective-C maintainers of the time weren't comfortable "locking down" their ABI to the extent required for direct linker-based references to class objects. (In this case, the reference and referent are in the same compilation unit, but whatever.) In any event, when the modern objc4 is in use, the compiler does emits a direct reference, bypassing the need for this particular on-launch step.

GCC's runtime

When Objective-C support was first added to GCC, the only compatible runtime was the (then-)closed-source NeXT runtime. That meant the compiler support was not meaningfully usable except on NeXTSTEP. Soon, though, some people volunteered to add a portable runtime to the GCC target libraries. (It's been rewritten a few times over the years.)

The GCC runtime has never been in popular use—even though Apple continued using GCC until around \~2011, they continued using (and adding to) the NeXT runtime described above. Understandably, GCC's runtime has therefore typically lagged behind the most recent changes to Objective-C, and it is currently missing some newer features, like non-fragile ivars and automatic reference counting.

One interesting advantage of GCC's runtime is that it uses a message-lookup rather than message-send primitive. The main benefit of this, at least for my purposes, is that a message-lookup routine can be written in (relatively portable) C, not (entirely nonportable) assembly.

This is because a message-send routine needs to (a) take a variable number of parameters of unknown types and (b) forward all of them verbatim to the actual method implementation (IMP). C supports variadic functions, of course, but the only way a function definition can access (and re-pass) those arguments would be through va_list. The underlying IMP isn't expecting a va_list, though, so that wouldn't work at all. So message-send routines are written directly in assembly, where this sort of argument forwarding is possible.

A message-lookup approach, on the other hand, simply looks up the IMP and returns its address to the calling code. The caller then calls the IMP itself, passing whatever arguments it wishes. The lookup routine never touches the arguments.[6][7]

In principle it seemed like the GCC runtime might be a good fit for this project. But its sheer size made that difficult.

Looking at nm, I calculated that libobjc.a contributes a just under 4k of data/BSS to the linked program.[8] My poor ATmega644p only has a total 4k of SRAM to speak of.[9] So once other bits (like my simple serial library), the linked program overflows the chip's memory. Yes—simply linking the runtime, without actually using it—into a "hello world" Objective-C program causes it not to boot!

Looking at nm showed that half of the memory usage was from class_table_array, a giant hash table that could reasonably be shrunk by changing a couple macros. That got me farther—able to run code, at least—but ultimately the runtime still crashed early in its initialization routine when it ran out of heap space.

So, while it might have been nice to use the GCC runtime, its memory usage just isn't tuned to work on such a small device.

Other runtimes

I looked around the web for other Objective-C runtimes, but I ultimately didn't consider any of them seriously. I suspect all of them would either be too large for my spartan environment or have some other problem. Some of them also are likely ABI-incompatible with modern GCC/clang, which would be another non-starter. They were interesting to read about, though, so here are they are:

  1. Earlier versions of the GCC runtime. According to this, there appear to be at least two earlier versions, though the README for the newest version claims that the "earlier runtime had several severe bugs and was rather incomplete."
  2. GNUstep's libobjc2. GNUstep, an open source implementation of OpenStep, originally used GCC's libobjc but switched to its own runtime, derived from work on Étoilé, around 2009. Also used by WinObjC, Microsoft's iOS-source-compatible application platform for Windows.
  3. mulle-objc
  4. Cocotron
  5. ObjFW. Formerly used by WinObjC.
  6. Appportable's fork of the public objc4 source
  7. Portable Object Compiler
  8. Stepstone's objcc and runtime

A minimal AVR-targeted runtime

Lacking what I saw as a perfect drop-in option, I decided to write my own runtime specifically for the AVR.

ABI choices

The three main runtimes I discussed above effectively define three runtime ABIs—rule sets that define the interface between compiler-generated code and the runtime.

For example, since the callsite usages of message-lookup and message-send routines differ, the compiler needs to know which one is in use when generating code. And if the runtime expects class references to be resolved by the linker, then the compiler needs to generate exported symbols for the class (and metaclass) objects.

Both GCC and clang support three major ABIs, corresponding to those three runtimes: the Apple modern objc4 runtime, the Apple/NeXT legacy objc4 runtime, and the GCC runtime. After extensive experimentation and debugging, I made notes on some of their differences to help decide which to try to implement in my runtime.

Runtime objc4 modern objc4 legacy GCC
Messenger type message-send message-send message-lookup
Main metadata location various Mach-O sections, e.g., `__objc_classlist` Mach-O section `__OBJC, __module_info` common symbol
Load-time runtime invocation automatic via dyld automatic via dyld each compilation unit calls `__objc_exec_class` with a pointer to its metadata struct
Selector refs indirected through string references, fixed up at load time
Message refs optional for vtable-based dispatch optimization, fixed up at first use of callsite not used
Class metadata separate `objc_class` and `class_ro` `objc_class` `objc_class`
Class refs indirected through pointers fixed up by static/dynamic linkers, except special cases (e.g., futured classes) fixed up again at runtime for messaging and inside class structs: string references, fixed up at load time for messaging: `objc_get_class()`; inside class structs: string references, fixed up at load time
Ivar accesses indirected using offsets fixed up at load time direct (fragile)

I decided to write to the objc4 (modern) ABI, for a few reasons. First: I like the code-size benefits of using a message-send routine (and of not using a class resolver at message time for class messages) on a memory-constrained device. (Also, this is a project for fun, and writing assembly is fun.) Second: each of these ABIs makes a tradeoff between compiler responsibility and runtime responsiblity; this one delegates enough to the compiler that I could get some initial messaging up and running without any load-time work. And finally: this ABI is what I'm most familiar with from my work on Mac OS X.


There are a lot of pieces that go into a fully featured Objective-C runtime, but obviously none of it is worth much without functional messaging.

The message send routine takes the target object, a selector, and the method arguments, and its job is to jump to the correct IMP, preserving all of the original arguments. There's plenty of trickiness just around marshalling the arguments, but first I needed a way to look up the IMP.

class_lookupMethod

As described earlier, a routine that looks up an IMP can be implemented in C. I wrote a function class_lookupMethod, which takes a class object and a selector and returns the corresponding IMP. An advanced runtime would implement a caching scheme to optimize the most common method dispatches, but mine just walks the compiler generated method list to find the IMP, without any caching.

The class data structures generated by the compiler aren't nicely documented, so you have to glean their layout from the source of an existing compiler or runtime. The exact format depends on what ABI is in use; here's what they look like for the objc4 modern ABI:

struct objc_class {
    struct objc_class *isa;
    struct objc_class *superclass;
    void *cache;
    void *vtable;
    struct class_ro *rodata;
};

struct class_ro {
    uint16_t flags;
    uint16_t instance_start;
    uint16_t instance_size;
    uint16_t reserved;
    uint8_t *ivarLayout;
    char *name;
    struct method_list *methods;
    void *protocols;
    struct ivar_list *ivars;    
    uint8_t *weakIvarLayout;
    struct property_list *properties;
};

struct method {
    SEL name;
    char *types;
    IMP imp;
};

struct method_list {
    uint16_t element_size;
    uint16_t element_count;
    struct method methods[];
};

The job, then, of class_lookupMethod, is to walk the methods array of the provided class, looking for a matching selector.

The binary format of selector references is an ABI detail, and in this case I knew they were just (non-uniqued) C strings. While the actual objc4 runtime overwrites those pointers-to-strings with unique selector "UIDs" at load time, I treated that as a performance optimization for later. That way, checking for a match is just a matter of using strcmp. Not necessarily fast, but simple.

Implementing basic inheritance here is simple: if no method is found for the given selector, advance to the superclass and keep looking.

One thing that this simple approach lacks is the ability to look up dynamically added or removed methods. Objective-C has a few different features that allow this; some would say that this particular dynamicism is one of the language's biggest strengths. For now, though, I limited my implementation to methods added at compile time to the main @implementation of the class.

The simplest messaging routine

With class_lookupMethod in hand, I was able to go ahead and write a basic messaging routine. All I needed was a way to invoke class_lookupMethod and then jump to the result, without disturbing any of the original arguments.

The avr-gcc calling convention is documented in this helpful wiki page. In short, arguments are passed in registers r8 through r25 (in descending order); any extra arguments are passed on the stack. Additionally, registers r2 through r17 and r28 and r29 (the latter two collectively the word register Y) are callee-saved (i.e., non-clobbered), so I can't use them as scratch space without saving and restoring them.

The AVR indirect-jump instruction doesn't take a register operand; instead, it assumes an implicit operand of Z (i.e., r31:r30). Since this is neither an argument register nor a callee-saved register, I can safely load the result of class_lookupMethod into Z and jump to it.

However, in order to call class_lookupMethod, though, I'll have to modify the argument registers. And class_lookupMethod may in turn clobber any of the argument registers not in the callee-save list, namely r18 through r25. (Since it's written in C, there's no future-proof way to know what registers it—or any downstream function—clobbers.)

Therefore, I need to save r18 through r25, plus any of the other callee-saved registers I want to use as scratch.

It's useful to extract that sort of code as a macro:

.macro PUSHARGS
    push r25
    push r24
    push r23
    push r22
    push r21
    push r20
    push r19
    push r18
.endm

.macro POPARGS
    pop r18
    pop r19
    pop r20
    pop r21
    pop r22
    pop r23
    pop r24
    pop r25
.endm

Then, my core message-send routine becomes quite simple:

objc_msgSend:
    ; self <- r25:r24
    ; _cmd <- r23:r22
    PUSHARGS

    ; X <- self (r25:r24)
    movw X, r24

    ; cls (r25:r24) <- *X
    ld r24, X+
    ld r25, X+

    ; _cmd remains in r23:r22
    call class_lookupMethod
    ; imp <- r25:r24

    ; Z <- imp (r25:r24)
    movw Z, r24

    POPARGS

    ijmp

Giving it a spin

By no means did I have a fully-fledged messaging routine at this point, but it should have been enough to test a simple program that sends a message to an object.

The first thing I needed is a root object class. This serves two purposes:

  1. It provides the isa ivar required for messaging to work
  2. It provides a +self method that we'll use to populate the isa of our object. In a real runtime, I might use objc_getClass() to achieve this. But I haven't implemented that yet (and it's not exactly trivial to do, as I'll show in a future entry)

Here's what the root object looks like:

@interface Object {
    Class _isa;
}

+ (Class)self;

@end

@implementation Object

+ (Class)self {
    return self;
}

@end

Next, I implemented a subclass with a couple methods:

@interface Window : Object {
    char *_title;
}

- (const char *)title;
- (void)setTitle:(const char *)title;

@end

@implementation Window

- (const char *)title {
    return _title;
}

- (void)setTitle:(const char *)title {
    free(_title);
    _title = strdup(title);
}

@end

All that remained was to create an instance and message it. In modern Objective-C, objects are almost universally[10] created on the heap, via +alloc, class_createInstance, and ultimately malloc. I haven't yet written class_createInstance, however, so an easier way is simply to create space for the object on the stack, using @defs:[11]

int main(void) {
    sei();
    serial_init();
    puts("boot\n\n");

    struct { @defs(Window) } memory;
    Window *const window = (Window *)&memory;
    window->_isa = [Window self];
    [window setTitle:"Window"];
    printf("window title: '%s'\n", [window title]);

    for (;;) ;
}

I built this, along with my class_lookupMethod from above, with -fnext-runtime -fobjc-abi-version=2 (to tell the compiler to use the objc4 modern runtime ABI) and -fno-objc-sjlj-exceptions (because the compiler requires this for that ABI, annoyingly crashing otherwise).

The compiler errored out with a million little cryptic messages like these:

ccXWvCt7.s: Assembler messages:
ccXWvCt7.s:23: Error: unknown opcode `_window._t'
ccXWvCt7.s:24: Error: unknown opcode `_window._t'
ccXWvCt7.s:44: Error: unknown opcode `_window._t'
ccXWvCt7.s:45: Error: unknown opcode `_window._t'
ccXWvCt7.s:51: Error: unknown opcode `_window._t'
ccXWvCt7.s:52: Error: unknown opcode `_window._t'
ccXWvCt7.s:98: Error: `)' required
ccXWvCt7.s:98: Error: unknown opcode `_window'
ccXWvCt7.s:99: Error: `)' required
(dozens more similar errors snipped)

What's going on there?

A small assembler fix

The first line tells us these are actually errors from the assembler—which GCC invoked on our behalf—not GCC itself. Unfortunately, the source file it's referencing is the assembler source that GCC generated, not any code I wrote directly. And by the time the command errors out, the generated source, which is placed in a temporary file, is gone.

However, I could easily ask GCC to recreate it, using the -S (i.e., "stop after generating assembly") option. I could then look at one of the offending lines—for example, line 23:

lds r30,OBJC_IVAR_$_Window._title

Here, the assembler is complaining that _window._title isn't a valid opcode (or rather, opcode mnemonic). In (most? all?) assembly languages, the opcode mnemonic is the part that comes at the beginning of each instruction line. So the assembler is erroring as if _window._title were the text at the beginning of the line. In the actual source, though, that text comes right after the $ sign.

Looking at other lines showed that all of the other errors similarly come right after a $.

Sec. 9.5.2.1 of the GNU assembler manual, says that $ is a special character in GNU's AVR assembler syntax. It's used as a line separator. So every assembly instruction referencing a symbol with a $—which the Objective-C compiler uses frequently (in objc4 modern ABI mode, at least) is being misinterpreted. Oops!

I'll omit the details for now, but I ended up having to make a change to the GNU assembler to allow disabling the $-as-newline behavior. Maybe I'll put that in a separate blog entry. The code is on GitHub.

Then I used GCC's -Wa, meta-option to force it to pass my new assembler option -mno-dollar-line-separator.

Edit (August 2021): this feature has been merged into mainline and should be released with Binutils 2.38.

Fixup messaging

With that fixed, the assembler succeeds, and I got a few linker errors:

ld: build/objc-test.o:(.data+0x52): undefined reference to `objc_msgSend_fixup'
ld: build/objc-test.o:(.data+0x56): undefined reference to `objc_msgSend_fixup'
ld: build/objc-test.o:(.data+0x5a): undefined reference to `objc_msgSend_fixup'
ld: build/objc-test.o:(.data+0x62): undefined reference to `_objc_empty_cache'
ld: build/objc-test.o:(.data+0x64): undefined reference to `_objc_empty_vtable'
ld: build/objc-test.o:(.data+0x90): undefined reference to `_objc_empty_cache'
ld: build/objc-test.o:(.data+0x92): undefined reference to `_objc_empty_vtable'
ld: build/objc-test.o:(.data+0xba): undefined reference to `_objc_empty_cache'
ld: build/objc-test.o:(.data+0xbc): undefined reference to `_objc_empty_vtable'
ld: build/objc-test.o:(.data+0xf8): undefined reference to `_objc_empty_cache'
ld: build/objc-test.o:(.data+0xfa): undefined reference to `_objc_empty_vtable'

The _objc_empty_cache and _objc_empty_vtable errors are easy to fix. The compiler is trying to emit the addresses of those symbols into class metadata. My messaging routine doesn't make use of either of these members, so all I needed to do is define dummy values for those symbols. A simple way to do that would just be to define two uint8_t globals, but a more elegant way would be to avoid consuming those two bytes of memory and instead define the symbols to zero with assembler directives:

.globl _objc_empty_cache
.equ _objc_empty_cache, 0
.globl _objc_empty_vtable
.equ _objc_empty_vtable, 0

objc_msgSend_fixup is more interesting. It's a remnant of a no-longer-used trick to optimize sending certain common selectors. Whereas a normal objc_msgSend takes the receiver object and a selector, the second parameter to a fixup message is a pointer to a struct:

struct message_ref {
    id (*messenger)(id self, SEL _cmd, ...);
    SEL _cmd;
};

Initially, messenger is objc_msgSend_fixup. The first time a particular struct message_ref is used, its messenger is changed. If _cmd is not one of the optimized selectors, then messenger is set to objc_msgSend_fixedup, which simply knows how to pick out the _cmd and pass it along to objc_msgSend.

But if _cmd is one of the optimized selectors, then messenger is set to an optimized messenger routine. This routine uses the class's vtable to look up the IMP, which is faster than a selector-based lookup.

This optimization is specific to the objc4 modern ABI. Since I told the compiler I was providing a runtime compliant with that ABI, it felt free to use it. However, as mentioned above, I'm not making use of this optimization, so I simply implemented objc_msgSend_fixup to behave exactly like objc_msgSend_fixedup:

objc_msgSend_fixup:
    ; X <- message_struct
    movw X, r22

    ; X <- &message_struct._cmd
    adiw X, 2

    ; _cmd <- *X
    ld r22, X+
    ld r23, X+

    jmp objc_msgSend

Incidentally, Apple itself no longer makes use of this optimization, choosing instead to optimize certain common selectors into direct runtime calls in the compiler. And since no other ABI supports this optimization, it seems like GCC should stop emitting these calls entirely.

First contact and next steps

With the fixup messenger implemented, the program built successfully. I uploaded the code to my board, and:

Screenshot of a terminal emulator showing my code running successfully.  "boot [newline] [newline] window title: 'Window'"

🎉🎉

I can't be 100% sure, but I think this might be the first successful execution of Objective-C on an AVR chip.

In my next entry, I'll discuss implementing some of the finer points of the messenger routine.


  1. The --with-avrlibc flag affects compilation of libgcc (the main compiler runtime library) directing the build system to omit certain floating point routines that are provided by avr-libc↩︎

  2. At one point, I tried tallying up the number of configuration languages involved in building GCC. Roughly in order of appearance: m4; autoconf; sh; sed (built by the configure script); autogen; make; awk; GCC specs; GCC multilib configuration; GCC machine description (Lisp). ↩︎

  3. The maintainer of this repository has done some admirable work to make recent open-source drops of objc4 buildable, ostensibly without Apple-internal access. I'm quite impressed! ↩︎

  4. Ivar fragility also in turn means no autosynthesized properties, ivars in implementation, etc., since the compiler of a subclass needs to know the full ivar layout up front. ↩︎

  5. Here's a wonderful description of Objective-C class objects. ↩︎

  6. Incidentally, Apple's runtime also supports message lookup via its objc_msgLookup entrypoint. This was added a few years back as an experiment to improve the performance of message dispatch. However, as of this writing, neither clang nor GCC offers a way to use objc_msgLookup↩︎

  7. Steve Naroff, original author of the NeXT runtime and GCC Objective-C frontend, describes this dichotomy nicely in this interview for the Computer History Museum. ↩︎

  8. And nearly 32k of text, too! ↩︎

  9. As of this writing, a beefier ATmega1284—with 128k of program flash and 16k of SRAM—is in the mail. ↩︎

  10. My favorite counterexample is AppKit's NSAnimationContext, the single instance of which exists in static data. (Do ignore the nonsense in the docs about stacked instances and similarity to NSGraphicsContext.) ↩︎

  11. Technically, I don't think @defs should be allowed in the modern runtime, because it wouldn't work under non-fragile ivars. Luckily, I hadn't implemented ivar relocation support yet, and GCC is perfectly happy to emit the @defs↩︎