The Catnip Prototype

March 22, 2019

So having talked a bit in my previous post about the rationale behind Catnip, I’d like to jump forward and introduce some results. Catnip is very much still a prototype, and I’d like to get more of the rough edges off before I actually release any code, but I’ve got an interesting little demo that shows off some of the key features at work.

This is Catnip running a little “Wolfenstein 3D”-style demo written in pure C# on a Gameboy Advance Micro (which, for reference, has a 16Mhz CPU and 256Kb of RAM):

Catnip on a GBA

This demo runs at around 20fps, and thanks to concurrent garbage collection has no framerate hiccups or stalls. Since the GBA has no 3D hardware, everything is being software rendered (the entire engine is C# code).

(as an aside, I find it very cool that unsafe blocks in C# even let you do things like this, so you can directly access hardware registers without needing any native support code)

unsafe
{
    joypadData = *((UInt16*)0x04000130); // Joypad input register
}

More to the point though, about now you’re probably asking “why would you ever want to do that?”, questioning my sanity, or both.

Obviously(?), I don’t really expect anyone to want to write a GBA game in C#. However, I wanted to try and set up a test to prove that Catnip is able to solve at least some of the target problems I set out in my last post, and building something that ran on a very low-spec system with no OS seemed a good way to achieve that. (and as a plus, I got to spend a day trying to remember how to write a Wolf3D-style renderer - shoutouts to Fabien Sanglard for his exceptionally useful book!)

I’m going to go into a bit more detail about how this works in some future posts, but for the moment here’s a few key features/facts:

  • Catnip has a dual-mode execution model - methods can be either interpreted (from a custom intermediate bytecode), or JITed into native code. In the case of the GBA demo, all of the code is converted to native code ahead of time for performance (and because RAM constraints mean that generating or loading code dynamically is a non-starter), but the interpreter can still be used if necessary.

  • Catnip’s JIT is implemented out-of-process as something I refer to as a “sideways JIT”. This means that the runtime converts methods to an internal bytecode which is then passed to an external program (on a host PC in the case of console or mobile targets) that in turn converts this to C++ code, and compiles that into a dynamic library using the appropriate compiler for the platform. This is then loaded by the runtime, and the methods involved switched dynamically from being interpreted to executed natively.

    This approach increases portability (as Catnip can leverage the existing C compiler’s native code generation), and allows JIT compilation on platforms where the security model blocks on-device generation of executable code. The extra latency introduced can be hidden by the fact that execution of the method can continue using the interpreter until the native code finishes loading.

  • Catnip is a “full fat” CLR implementation, with the majority of major features (generics, exceptions, P/Invoke, etc) supported. Reflection is only very partially supported at the moment, and a couple of non-critical features (code verification, remoting) haven’t been implemented at all. It is using a very-slightly-modified version of the CoreCLR base class libraries, so the vast majority of library functionality “just works”.

  • Garbage collection is done in a concurrent fashion, with GC operations time-sliced into each frame (taking about 1~2ms). Write barriers are used to ensure that object mutations caused by the game code don’t cause the GC to erroneously collect live objects.

  • Assemblies are loaded and processed offline to generate a “warm boot image”, which contains both the original assembly and Catnip’s generated data (structure layouts and the like). This allows the demo to boot quickly (a few seconds), and avoids using up RAM for these structures.

Whilst this demo may seem a bit odd, I think it serves to demonstrate that with a bit of work the CLR (and by extension C#) can be used effectively even in very resource-constrained environments, and that the fundamental approach I’ve taken with Catnip is a viable way to achieve this.

If you’ve got any questions or thoughts on this topic, please just drop me an email at contact@shironekolabs.com!