.NET application is “just” a piece of CIL bytecode to be executed by the .NET runtime. And .NET runtime is “just” a program that is able to perform this task. It happens that currently .NET Framework/.NET Core runtimes are written in C++. I am also fully aware of CoreRT that was .NET runtime with many parts rewritten to C# (like type system) but still, crucial parts (including JIT compiler and the GC) were left written in C++.
But what if we write .NET runtime as… .NET application? Is is possible at all? I mean, literally no native/C++ code, everything running as .NET Core application written in C#? Does this sound like kind of inception and infinite recursion? It would require running one .NET runtime on the top of another .NET runtime, right?
I decided to check it out and that’s how Mobius runtime idea has been coined! Yeah, I know it sound strange and I do not expect it will be anything close to production ready thingy in the nearest century. I am fully aware of the amount of code needed to be written to make full .NET runtime. However, I found it interesting to validate such idea and I find it small usages as well. Imagine a NuGet package with the separate runtime that you can add to your application 😉
There were similar attempts in other environments, most notably Jikes RVM – JVM virtual machine written in Java. Although it still requires some C-based bootstraping, and is self-hosted (its Java code runs on itself without requiring a second virtual machine), it is very close to what I have in mind with Mobius. As the project website explains (emphasis mine):
Jikes RVM (Research Virtual Machine) provides a flexible open testbed to prototype virtual machine technologies and experiment with a large variety of design alternatives. (…) A Java implementation provides ease of portability, and a seamless integration of virtual machine and application resources such as objects, threads, and operating-system interfaces. (…) Many researchers have found that Jikes RVM provides a useful vehicle for research on the frontiers of virtual machine technologies (over 188 publications and 36 dissertations)
And obviously there is GraalVM with approach similar to CoreRT (JIT and most parts written in Java). Even in the .NET Core itself, more and more parts are being ported to the managed side. Ongoing discussions compare them within the .NET environment, please refer for example to Port JIT and GC to C# issue.
So in summary, Mobius is:
- for research and experiments – having runtime code in C#/.NET, we can easier and faster prototype various parts of it, like various JIT or GC implementation changes. We can even experiment with writing all or parts of the runtime in functional language like F#. That’s exactly the same rationale like in Jikes RVM.
- for learning – we can learn a lot about the structure of the runtime and its internal dependencies. It is also just so perfect scenario for utilizing modern C# capabilities, low-level APIs (Span, stackallock, Unsafe class). I’ve even already posted one .NET Core issue (DynamicMethod calls without inlining #34500) that came out during work on Mobius.
- for performance – this is obviously the most controversial 🙂 Running one managed runtime on the another managed runtime does not sound like anything related to performance. But, after applications warms up (most methods have been already JITted), in the end what matters is the quality of the native code generated by the JIT. Written in C# in this case, theoretically allows more robust optimizations development. And there is the GC, while written in C#, that theoretically can also benefit the same.
- for fun – for some, including me, it can be just pure fun to see its working! A perfect, not obliging pet project.
Note. I’m fully aware those reasons are not very strong. Mostly, one can argue that C++ development vs C# development is not so a big difference and it doesn’t explain rewriting the whole runtime. I fully agree and that’s why I treat the whole thing as a toy and experiment for learning!
Ok, putting aside the discussion whether it is worth doing, let’s think how to do it.
First of all, the most important thing to understand is that Mobius still JIT compiles IL of you application to the native code. In that manner, in the end, you application is running at (almost) the native code speed. The difference is that the managed infrastructure (like the GC, type system, JIT compiler), called from the JITted code occasionally (like allocations), is running as managed code. But it means it will be JITted either (by .NET Core).
In the end, we have two layers of JITted code and the native code of the underlying, real runtime. This additional layer of Mobius JITted code seems to be an overkill, but it really depends on how intensively Mobius uses .NET Core facilities. If it is mostly written in an alloc-free manner, there are almost no GC calls. In a warmed up state, JIT calls will be also rare. This allows to assume that in a regularly working application the most work will be done in the app JITted code, with some Mobius JITted calls and very occasional .NET Core native calls.
When looking at the above graph, duplication clearly stands out. One could think of sharing some managed facilities between both runtimes.
In the end, rewriting the whole type system is not such a big fun. We could even think of reusing the same GC in Mobius, so objects provided by Mobius to the .NET application would be living on the good, old .NET Core Managed Heap. Although it seems tempting as it reduces a lot of work, I see two main problems with this approach:
- it reduces “research and experiments” part of the Mobius purpose a lot – instead of inventing/experimenting on GC and JIT algorithms, we will be forced to think how to incorporate the already existing ones,
- JIT, GC and type system contracts are pretty coupled – it could reduce “research and experiments” part to minimum, leaving us with almost no other choice than reimplement the same mechanism in Mobius
In the end, how I would like to see it, is think as .NET Core runtime providing only occasional services to the Mobius runtime, mostly just calling application JITted code.
Although I definitely plan to open source all the work, currently I’m working on vertical proof-of-concept that will be able to run the simplest C# application:
private static int Main(string args)
int num = 1;
int num2 = 2;
return num + num2;
I see the light at the end of the tunnel. Currently all required mechanisms are in place: JITting basics, calling JITTed code from managed code, calling Mobius from the JITted code. I need some more time to refactor, polish and implement a lot of metadata handling (to get rid of some boldly hard-coded values :)).
By vertical I mean, it will be able to literally run this and only this single application, with all CIL instructions required, with no exception handling, with only GC-stubs.
In the next series of articles I will present more details and code snippets of the most low-level part of the Mobius implementation.