Bringing Objective-C to the Amiga



After porting ObjFW (and at the same time Objective-C) to MorphOS and starting to port it to AmigaOS 4, I thought: It's nice to have Objective-C on a modern Amiga-like operating system. But what if we could have it on the real thing, the classic m68k Amiga? And thus, I ported it to AmigaOS 3 today.

First things first, we need a compiler. No compiler modern enough to support exceptions in Objective-C was released for the Amiga itself, as there was never any GCC 4 for Amiga. However, there's a new cross-compiler around: bebbo's amiga-gcc. This is the successor to his amiga-cross-toolchain fork, which unfortunately downloaded lots and lots of archives via plain insecure HTTP and would then go to execute downloaded files, so I never really wanted that on my system. But amiga-gcc checks out everything from GitHub, and the few files it downloads never get executed on the system you build the toolchain on. So after some minor patches to make it build on macOS, I had a toolchain.

Getting ObjFW to compile was easy. But running the resulting tests binary immediately resulted in "Program aborted". Huh, that was strange - I was already expecting exceptions not to really work, but then the first test using exceptions would usually fail and not an immediate abort. So now I had to figure out what was aborting - not an easy task without having a debugger in which I can just set a breakpoint and look at a backtrace. So the first thing to try was creating an empty binary that just links in ObjFW and its runtime, to see if all the initialization worked. That worked, so I added exceptions into the mix. Now I got the same "Progam aborted" as with the tests. So I moved on to to add a #define for abort to print the source file and line and then exit. The first theory was that one of the switch es in src/runtime/exception.m fell through and then called abort() . But this didn't catch anything, meaning it's not in my code. Usual suspects then would be libc and libgcc. So I went ahead and added the same #define to the libgcc sources and finally found what called abort: uw_init_context() . Ouch, that's bad, that's pretty much the first thing called by _Unwind_RaiseException() . uw_frame_state_for() returned _URC_END_OF_STACK , making an assert in uw_init_context() fail. Well, that should never happen, unless our DWARF info is garbage. I had tried exceptions with C++ before, which worked, so I knew that unwinding couldn't be entirely broken. Looking at the assembly output of a small test program that just throws and catches in both C++ and ObjC, there was no obvious difference. After trying several things for hours, including rebuilding the toolchain with Obj-C++ support and trying that and switching from amiga-cross-toolchain to amiga-gcc, which all failed, I finally found a way to make it work: When using m68k-amigaos-g++ rather than m68k-amigaos-gcc , it would suddenly work. I had tried compiling it as ObjC++ before by renaming the the testcase to .mm , but that didn't work. But naming it .m and using m68k-amigaos-g++ , it worked. I have not figured out why it works this way, though, but at least now I have something that works. And according to m68k-amigaos-nm , it does not even bring any unnecessary C++ code in.

Shortly after this was working, I had almost all tests work. Except one weird one where the serialization was breaking. After spending some hours on this, I finally found the culprit: I had added my own defines for PRI?{8,16,32,64} , as I was initially using amiga-cross-toolchain, but switched to amiga-gcc while debugging the exception problem. amiga-cross-toolchain lacked those, but amiga-gcc actively undefined them, redefining them the wrong way, truncating the result to 32 bit. The solution here was to #define __have_longlong64 . And after some more fixes, I then finally got this: